[FW][MERGE] website_event_jitsi, _*: improve chat room management and provide some fixes
PURPOSE Provide some fixes after internal test deployment of event online features. Notably: chat room management and track list. See sub commits for more details. UPDATE Templates * website_event_meet.meeting_room_card * website_event_meet.community_aside * website_event_meet.meeting_room_main * website_event_track_session.event_track_content * website_event_track_session.event_track_aside_other_track Views * website_event_meet.event_meeting_room_view_tree * website_event_track_online.event_sponsor_view_tree * website_event_track_exhibitor.event_sponsor_view_tree SPECIFICATIONS: TRACKS LIST It allows to better know who the speaker is. This is done through a related on the speaker function and partner_name fields. It allows to display a sentence like "Director at Azure Interior", which gives information on both its position and its company. SPECIFICATIONS: CHAT ROOMS Clean code managing chat rooms: handle most of its management in chat room mixin. It allows to lessen custom code in website_event_meet. Improve sanitize heuristic of chat rooms by avoiding name collision. If a room with a given name already exists, simply increment a counter as suffix. Improve copy management: when copying from the chat.room.mixin on a record with a room set, simply propagate an updated room name. As room_id itself is not kept but a new one is create, it will create a new room with a name avoiding collisions. Allow multi edit for event meeting rooms and exhibitors. It allows notably to publish by batch, and to set exhibitors as having chat rooms by batch. Give default values for max participants count when creating exhibitors so that required field has a default value. Otherwise people forgot it and have to come back on form view to set it. This is due to default value coming from mixin not being propagated to the form view. LINKS Coming from internal feedback while deploying online support Task ID-2314778 PR #55887 closes odoo/odoo#55949 Forward-port-of: odoo/odoo#55887 Forward-port-of: odoo/odoo#55941 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
@@ -1,9 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from werkzeug.urls import url_join
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo import api, fields, models
|
||||
from odoo.addons.http_routing.models.ir_http import slug
|
||||
|
||||
|
||||
@@ -21,17 +19,10 @@ class EventMeetingRoom(models.Model):
|
||||
is_published = fields.Boolean(copy=True) # make the inherited field copyable
|
||||
event_id = fields.Many2one("event.event", string="Event", required=True, ondelete="cascade")
|
||||
is_pinned = fields.Boolean("Is Pinned")
|
||||
summary = fields.Char("Summary")
|
||||
chat_room_id = fields.Many2one("chat.room", required=True, ondelete="restrict")
|
||||
summary = fields.Char("Summary", translate=True)
|
||||
target_audience = fields.Char("Audience", translate=True)
|
||||
|
||||
# TDE FIXME: merge with mixin code
|
||||
ROOM_CONFIG_FIELDS = {
|
||||
'room_name': 'name',
|
||||
'room_lang_id': 'lang_id',
|
||||
'room_max_capacity': 'max_capacity',
|
||||
'room_participant_count': 'participant_count',
|
||||
}
|
||||
|
||||
@api.depends('name', 'event_id.name')
|
||||
def _compute_website_url(self):
|
||||
super(EventMeetingRoom, self)._compute_website_url()
|
||||
@@ -42,19 +33,7 @@ class EventMeetingRoom(models.Model):
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, values_list):
|
||||
# TDE FIXME: merge with mixin code
|
||||
for values in values_list:
|
||||
if not values.get("chat_room_id"):
|
||||
# be sure to always create a `chat.room` for each `event.meeting.room`
|
||||
chat_room = self.env["chat.room"].create({
|
||||
self.ROOM_CONFIG_FIELDS[field]: value
|
||||
for field, value in dict(values).items()
|
||||
if field in self.ROOM_CONFIG_FIELDS and values.pop(field)
|
||||
})
|
||||
|
||||
values["chat_room_id"] = chat_room.id
|
||||
|
||||
values["name"] = values["name"].capitalize()
|
||||
values["target_audience"] = (values.get("target_audience") or _("Attendee(s)")).capitalize()
|
||||
|
||||
if not values.get("chat_room_id") and not values.get('room_name'):
|
||||
values['room_name'] = 'odoo-room-%s' % (values['name'])
|
||||
return super(EventMeetingRoom, self).create(values_list)
|
||||
|
||||
@@ -38,8 +38,9 @@ class TestWebsiteEventMeet(TestEventCommon):
|
||||
"target_audience": "dev",
|
||||
"room_max_capacity": "20",
|
||||
})
|
||||
|
||||
self.assertEqual(meeting_room_1.room_name, 'odoo-room-test-meeting-room')
|
||||
meeting_room_2 = meeting_room_1.copy()
|
||||
self.assertEqual(meeting_room_2.room_name, 'odoo-room-test-meeting-room-1')
|
||||
|
||||
chat_room_1 = meeting_room_1.chat_room_id
|
||||
chat_room_2 = meeting_room_2.chat_room_id
|
||||
@@ -51,6 +52,9 @@ class TestWebsiteEventMeet(TestEventCommon):
|
||||
self.assertEqual(chat_room_1.max_capacity, "20", "Must set the max capacity on the chat room")
|
||||
self.assertEqual(chat_room_2.max_capacity, "20", "Must copy the max capacity")
|
||||
|
||||
meeting_room_3 = meeting_room_1.copy()
|
||||
self.assertEqual(meeting_room_3.room_name, 'odoo-room-test-meeting-room-2')
|
||||
|
||||
def test_meeting_room_unlink(self):
|
||||
"""Test the duplication of the meeting room."""
|
||||
meeting_room = self.env["event.meeting.room"].create({
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
<div class="o_wevent_meeting_room_corner_ribbon" t-if="meeting_room.room_is_full">Full</div>
|
||||
<div class="d-flex flex-column">
|
||||
<div class="d-flex flex-row justify-content-between">
|
||||
<h4 class="text-break mw-75" t-esc="meeting_room.name"/>
|
||||
<h4 class="text-break mw-75 text-uppercase" t-esc="meeting_room.name"/>
|
||||
<div t-if="is_event_manager" class="w-25">
|
||||
<div class="dropdown float-right dropleft">
|
||||
<button class="btn py-0" data-toggle="dropdown"><h3 class="m-0">⋮</h3></button>
|
||||
@@ -133,9 +133,9 @@
|
||||
<span t-esc="meeting_room.summary" class="h5 text-muted"/>
|
||||
<div class="d-flex flex-row justify-content-between align-items-center">
|
||||
<span class="h6 m-0 row">
|
||||
<span class="o_wevent_participant_count">
|
||||
<t t-esc="meeting_room.room_participant_count"/>&nbsp;</span>
|
||||
<t t-esc="meeting_room.target_audience or 'participant(s)'"/>
|
||||
<span t-esc="meeting_room.room_participant_count"/>&nbsp;
|
||||
<span t-if="meeting_room.target_audience" class="text-uppercase" t-field="meeting_room.target_audience"/>
|
||||
<span t-else="" class="text-uppercase">participant(s)</span>
|
||||
</span>
|
||||
<div class="d-inline border py-1 px-2 bg-secondary" t-esc="meeting_room.room_lang_id.name"/>
|
||||
</div>
|
||||
@@ -155,11 +155,18 @@
|
||||
<hr class="mt-2 mb-3"/>
|
||||
<p>Want to create your own discussion room ?</p>
|
||||
<p>Be sure you are ready to spend at least 10 minutes in the room if you want to initiate a new topic.</p>
|
||||
<a href="#" role="button" class="btn btn-primary o_wevent_create_room_button"
|
||||
<a href="#" role="button"
|
||||
class="btn btn-primary o_wevent_create_room_button"
|
||||
t-if="event.is_ongoing or event.start_today or is_event_manager"
|
||||
t-att-data-event-id="event.id"
|
||||
t-att-data-default-lang-code="default_lang_code">
|
||||
<span>Create a Room</span>
|
||||
</a>
|
||||
<div t-else="" class="d-flex flex-column">
|
||||
<button disabled="disabled" class="btn btn-primary align-self-start">Create a Room</button>
|
||||
Room creation will be available when event starts at
|
||||
<span class="font-weight-bold" t-field="event.with_context(tz=event.date_tz).date_begin" t-options="{'format': 'medium'}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
t-options="{'format': 'medium'}"/> (<t t-esc="meeting_room.event_id.date_tz"/>).
|
||||
</span>
|
||||
<br/>
|
||||
<span>Join us there to chat about "<b t-esc="meeting_room.name"/> !</span>
|
||||
<span>Join us there to chat about <b t-esc="meeting_room.name"/> !</span>
|
||||
</div>
|
||||
<!-- ROOM CONTENT -->
|
||||
<div class="d-flex flex-column">
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<field name="is_pinned"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="chat_room_id"/>
|
||||
<field name="chat_room_id" required="0"/>
|
||||
<field name="room_participant_count" readonly="1"/>
|
||||
<field name="room_max_capacity" widget="radio" options="{'horizontal': true}"/>
|
||||
<field name="room_lang_id" options="{'no_create': True}"/>
|
||||
@@ -58,14 +58,14 @@
|
||||
<field name="name">event.meeting.room.tree</field>
|
||||
<field name="model">event.meeting.room</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Meeting Room">
|
||||
<tree string="Meeting Room" multi_edit="1">
|
||||
<field name="name"/>
|
||||
<field name="summary"/>
|
||||
<field name="target_audience"/>
|
||||
<field name="is_published"/>
|
||||
<field name="is_pinned"/>
|
||||
<field name="room_is_full"/>
|
||||
<field name="room_participant_count"/>
|
||||
<field name="room_is_full" readonly="1"/>
|
||||
<field name="room_participant_count" readonly="1"/>
|
||||
<field name="room_max_capacity"/>
|
||||
<field name="room_lang_id"/>
|
||||
</tree>
|
||||
|
||||
@@ -53,9 +53,12 @@ class EventSponsor(models.Model):
|
||||
for sponsor in self:
|
||||
if sponsor.is_exhibitor and not sponsor.room_name:
|
||||
if sponsor.name:
|
||||
sponsor.room_name = self.env['chat.room']._jitsi_sanitize_name("odoo-exhibitor-%s" % sponsor.name)
|
||||
room_name = "odoo-exhibitor-%s" % sponsor.name
|
||||
else:
|
||||
sponsor.room_name = self.env['chat.room']._default_name()
|
||||
room_name = self.env['chat.room']._default_name(objname='exhibitor')
|
||||
sponsor.room_name = self._jitsi_sanitize_name(room_name)
|
||||
if sponsor.is_exhibitor and not sponsor.room_max_capacity:
|
||||
sponsor.room_max_capacity = '8'
|
||||
|
||||
@api.depends('partner_id')
|
||||
def _compute_website_description(self):
|
||||
@@ -97,6 +100,10 @@ class EventSponsor(models.Model):
|
||||
else:
|
||||
sponsor.country_flag_url = False
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# MIXINS
|
||||
# ---------------------------------------------------------
|
||||
|
||||
@api.depends('name', 'event_id.name')
|
||||
def _compute_website_url(self):
|
||||
super(EventSponsor, self)._compute_website_url()
|
||||
@@ -105,13 +112,32 @@ class EventSponsor(models.Model):
|
||||
base_url = sponsor.event_id.get_base_url()
|
||||
sponsor.website_url = '%s/event/%s/exhibitor/%s' % (base_url, slug(sponsor.event_id), slug(sponsor))
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# CRUD
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, values_list):
|
||||
for values in values_list:
|
||||
if values.get('is_exhibitor') and not values.get('room_name'):
|
||||
name = values['name'] if values.get('name') else self.env['res.partner'].browse(values['partner_id']).name or 'sponsor'
|
||||
values['room_name'] = self.env['chat.room']._jitsi_sanitize_name(name)
|
||||
exhibitor_name = values['name'] if values.get('name') else self.env['res.partner'].browse(values['partner_id']).name
|
||||
name = 'odoo-exhibitor-%s' % exhibitor_name or 'sponsor'
|
||||
values['room_name'] = name
|
||||
return super(EventSponsor, self).create(values_list)
|
||||
|
||||
def write(self, values):
|
||||
toupdate = self.env['event.sponsor']
|
||||
if values.get('is_exhibitor') and not values.get('chat_room_id') and not values.get('room_name'):
|
||||
toupdate = self.filtered(lambda exhibitor: not exhibitor.chat_room_id)
|
||||
# go into sequential update in order to create a custom room name for each sponsor
|
||||
for exhibitor in toupdate:
|
||||
values['room_name'] = 'odoo-exhibitor-%s' % exhibitor.name
|
||||
super(EventSponsor, exhibitor).write(values)
|
||||
return super(EventSponsor, self - toupdate).write(values)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# ACTIONS
|
||||
# ---------------------------------------------------------
|
||||
|
||||
def get_backend_menu_id(self):
|
||||
return self.env.ref('event.event_main_menu').id
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<field name="inherit_id" ref="website_event_track_online.event_sponsor_view_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='sponsor_type_id']" position="after">
|
||||
<field name="is_published" optional="show"/>
|
||||
<field name="is_exhibitor"/>
|
||||
</xpath>
|
||||
</field>
|
||||
|
||||
@@ -17,6 +17,12 @@ class Track(models.Model):
|
||||
partner_biography = fields.Html(
|
||||
string='Biography', compute='_compute_partner_biography',
|
||||
readonly=False, store=True)
|
||||
partner_function = fields.Char(
|
||||
'Job Position', related='partner_id.function',
|
||||
compute_sudo=True, readonly=True)
|
||||
partner_company_name = fields.Char(
|
||||
'Company Name', related='partner_id.parent_name',
|
||||
compute_sudo=True, readonly=True)
|
||||
image = fields.Image(
|
||||
string="Speaker Photo", compute="_compute_speaker_image",
|
||||
readonly=False, store=True,
|
||||
|
||||
@@ -18,6 +18,12 @@
|
||||
<field name="model">event.sponsor</field>
|
||||
<field name="inherit_id" ref="website_event_track.event_sponsor_view_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//tree" position="attributes">
|
||||
<attribute name="multi_edit">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='partner_id']" position="attributes">
|
||||
<attribute name="readonly">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='partner_id']" position="after">
|
||||
<field name="name"/>
|
||||
<field name="email"/>
|
||||
|
||||
@@ -100,6 +100,12 @@
|
||||
t-options="{'widget': 'image', 'class': 'rounded-circle', 'max_width': '96'}"/>
|
||||
<div class="px-3 d-flex flex-column">
|
||||
<span t-field="track.partner_name" class="font-weight-bold mb-2"/>
|
||||
<span class="mb-1 d-flex align-items-baseline text-break" t-if="track.partner_function">
|
||||
<i class="fa fa-briefcase mr-2"/><span t-esc="track.partner_function"/>
|
||||
<t t-if="track.partner_company_name">
|
||||
<span>&nbsp;at&nbsp;</span><span t-esc="track.partner_company_name"/>
|
||||
</t>
|
||||
</span>
|
||||
<span class="mb-1 d-flex align-items-baseline text-break" t-if="track.partner_id.website">
|
||||
<i class="fa fa-home mr-2"/><a t-att-href="track.partner_id.website"><span t-field="track.partner_id.website"/></a>
|
||||
</span>
|
||||
@@ -183,7 +189,7 @@
|
||||
<template id="event_track_aside_other_track">
|
||||
<span t-esc="track_other.name" class="w-100"/>
|
||||
<div class="d-flex align-items-center">
|
||||
<small class="text-muted" t-esc="track_other.partner_id.name"/>
|
||||
<small class="text-muted" t-esc="track_other.partner_name"/>
|
||||
<div class="d-inline-block ml-auto o_wesession_track_aside_info text-truncate">
|
||||
<small t-if="track_other.is_track_live and not track_other.is_track_done"
|
||||
class="badge badge-danger">Live</small>
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import re
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.tools import remove_accents
|
||||
|
||||
|
||||
class ChatRoom(models.Model):
|
||||
@@ -22,8 +19,8 @@ class ChatRoom(models.Model):
|
||||
_name = "chat.room"
|
||||
_description = "Chat Room"
|
||||
|
||||
def _default_name(self):
|
||||
return "odoo-room-%s" % str(uuid4())[:8]
|
||||
def _default_name(self, objname='room'):
|
||||
return "odoo-%s-%s" % (objname, str(uuid4())[:8])
|
||||
|
||||
name = fields.Char(
|
||||
"Room Name", required=True, copy=False,
|
||||
@@ -62,6 +59,3 @@ class ChatRoom(models.Model):
|
||||
|
||||
for room in self:
|
||||
room.jitsi_server_domain = jitsi_server_domain
|
||||
|
||||
def _jitsi_sanitize_name(self, name):
|
||||
return re.sub(r'[^\w+.]+', '-', remove_accents(name).lower())
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
import re
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.tools import remove_accents
|
||||
|
||||
class ChatRoomMixin(models.AbstractModel):
|
||||
"""Add the chat room configuration (`chat.room`) on the needed models.
|
||||
@@ -28,30 +30,48 @@ class ChatRoomMixin(models.AbstractModel):
|
||||
room_max_capacity = fields.Selection(string="Max capacity", related="chat_room_id.max_capacity", readonly=False, required=True)
|
||||
room_participant_count = fields.Integer("Participant count", related="chat_room_id.participant_count", readonly=False)
|
||||
room_last_activity = fields.Datetime("Last activity", related="chat_room_id.last_activity")
|
||||
room_max_participant_reached = fields.Integer("Max participant reached", related="chat_room_id.max_participant_reached")
|
||||
room_max_participant_reached = fields.Integer("Peak participants", related="chat_room_id.max_participant_reached")
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, values_list):
|
||||
for values in values_list:
|
||||
if any(values.get(fmatch[0]) for fmatch in self.ROOM_CONFIG_FIELDS) and not values.get('chat_room_id'):
|
||||
room_values = dict((fmatch[1], values[fmatch[0]]) for fmatch in self.ROOM_CONFIG_FIELDS if values.get(fmatch[0]))
|
||||
if values.get('room_name'):
|
||||
values['room_name'] = self._jitsi_sanitize_name(values['room_name'])
|
||||
room_values = dict((fmatch[1], values.pop(fmatch[0])) for fmatch in self.ROOM_CONFIG_FIELDS if values.get(fmatch[0]))
|
||||
values['chat_room_id'] = self.env['chat.room'].create(room_values).id
|
||||
return super(ChatRoomMixin, self).create(values_list)
|
||||
|
||||
def write(self, values):
|
||||
if any(values.get(fmatch[0]) for fmatch in self.ROOM_CONFIG_FIELDS):
|
||||
if values.get('room_name'):
|
||||
values['room_name'] = self._jitsi_sanitize_name(values['room_name'])
|
||||
for document in self.filtered(lambda doc: not doc.chat_room_id):
|
||||
room_values = dict((fmatch[1], values[fmatch[0]]) for fmatch in self.ROOM_CONFIG_FIELDS if values.get(fmatch[0]))
|
||||
document.chat_room_id = self.env['chat.room'].create(room_values).id
|
||||
return super(ChatRoomMixin, self).write(values)
|
||||
|
||||
def copy(self, default=None):
|
||||
def copy_data(self, default=None):
|
||||
if default is None:
|
||||
default = {}
|
||||
if self.chat_room_id:
|
||||
chat_room_id = self.chat_room_id.copy()
|
||||
default = dict(default or {}, chat_room_id=chat_room_id.id)
|
||||
return super(ChatRoomMixin, self).copy(default)
|
||||
chat_room_default = {}
|
||||
if 'room_name' not in default:
|
||||
chat_room_default['name'] = self._jitsi_sanitize_name(self.chat_room_id.name)
|
||||
default['chat_room_id'] = self.chat_room_id.copy(default=chat_room_default).id
|
||||
return super(ChatRoomMixin, self).copy_data(default=default)
|
||||
|
||||
def unlink(self):
|
||||
if self.chat_room_id:
|
||||
self.chat_room_id.unlink()
|
||||
return super(ChatRoomMixin, self).unlink()
|
||||
rooms = self.chat_room_id
|
||||
res = super(ChatRoomMixin, self).unlink()
|
||||
rooms.unlink()
|
||||
return res
|
||||
|
||||
def _jitsi_sanitize_name(self, name):
|
||||
sanitized = re.sub(r'[^\w+.]+', '-', remove_accents(name).lower())
|
||||
counter, sanitized_suffixed = 1, sanitized
|
||||
existing = self.env['chat.room'].search([('name', '=like', '%s%%' % sanitized)]).mapped('name')
|
||||
while sanitized_suffixed in existing:
|
||||
sanitized_suffixed = '%s-%d' % (sanitized, counter)
|
||||
counter += 1
|
||||
return sanitized_suffixed
|
||||
|
||||
Reference in New Issue
Block a user