summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorstd-odoo <std@odoo.com>2020-07-13 10:59:07 +0000
committerThibault Delavallée <tde@odoo.com>2020-08-04 14:28:29 +0000
commitc918c58213e9dd5f3bf6ac0da1cb4f2afdb15d3a (patch)
tree7dd6706558c60bf5f4521d6d7d1278a6a61aa028
parentb990f71de0b8f2ad3cda07c1d34b01a6e5f1b3d3 (diff)
[ADD] website_event_meet: allow visitors to meet at online events
RATIONALE Events are sometimes held online, gathering a community. In this merge we improve Event application to better support full-online events with improved tracks, wishlists, chat rooms, ... PURPOSE Allow the website visitors to meet at online events using chat rooms. Those are based on jitsi rooms previously added. SPECIFICATIONS Define a new model holding discussion rooms, with their target and a publish management. Inherit from chat.room mixin to enable jitsi-based chat. Any website visitor can create a meeting room based on Jitsi. Using some information (name, summary, room size, languyage) anyone can launch a new discussion topic. This will make a link with a new jitsi room enabling people to chat. Visitor-community rooms creation can be turned off on event itself. Meeting rooms can be pinned by the event managers to choose which rooms are displayed on top. Managers can also duplicate the full meeting rooms if a topic is popular, or publish/unpublish rooms like usual website records. SPECIFICATIONS: LIST VIEW Display a card-based list view of meeting rooms. They can be filtered by language. When a meeting room reach the maximum number of participants it is not visible anymore for standard visitors. The meeting rooms unpinned are cleaned after a certain period of time if there is no activity in it usign the autovacuum cron. That way we keep a clean view on the frontend and we keep the important (pinned) meeting rooms. SPECIFICATIONS: PAGE VIEW Use the same main content / aside panel display as exhibitors / tracks, to display the jitsi in the main content. Left panel proposes suggestions of rooms, based on current one. When a visitor join a meeting room, we fill the jitsi username with the name he entered during his registration. LINKS Task ID-2252655 (Main Online Event task) Task ID-2283742 (Community gathering on chat rooms) Co-Authored-By: Aurélien Warnon <awa@odoo.com> Co-Authored-By: David Beguin <dbe@odoo.com> Co-Authored-By: Stéphane Debauche <std@odoo.com> Co-Authored-By: Thibault Delavallée <tde@odoo.com>
-rw-r--r--addons/website_event_meet/__init__.py3
-rw-r--r--addons/website_event_meet/__manifest__.py16
-rw-r--r--addons/website_event_meet/controllers/__init__.py5
-rw-r--r--addons/website_event_meet/controllers/main.py162
-rw-r--r--addons/website_event_meet/controllers/website_event_main.py25
-rw-r--r--addons/website_event_meet/data/website_event_meet_demo.xml42
-rw-r--r--addons/website_event_meet/models/__init__.py8
-rw-r--r--addons/website_event_meet/models/event_event.py42
-rw-r--r--addons/website_event_meet/models/event_meeting_room.py60
-rw-r--r--addons/website_event_meet/models/event_type.py18
-rw-r--r--addons/website_event_meet/models/ir_autovacuum.py24
-rw-r--r--addons/website_event_meet/models/website_event_menu.py10
-rw-r--r--addons/website_event_meet/security/ir.model.access.csv6
-rw-r--r--addons/website_event_meet/security/security.xml15
-rw-r--r--addons/website_event_meet/static/src/js/customize_options.js69
-rw-r--r--addons/website_event_meet/static/src/js/website_event_create_meeting_room_button.js59
-rw-r--r--addons/website_event_meet/static/src/js/website_event_meeting_room.js111
-rw-r--r--addons/website_event_meet/static/src/scss/event_meet_templates.scss59
-rw-r--r--addons/website_event_meet/static/src/xml/customize_options.xml14
-rw-r--r--addons/website_event_meet/static/src/xml/website_event_meeting_room.xml54
-rw-r--r--addons/website_event_meet/tests/__init__.py4
-rw-r--r--addons/website_event_meet/tests/test_website_event_meet.py67
-rw-r--r--addons/website_event_meet/views/assets.xml11
-rw-r--r--addons/website_event_meet/views/event_event_views.xml21
-rw-r--r--addons/website_event_meet/views/event_meet_templates_list.xml167
-rw-r--r--addons/website_event_meet/views/event_meet_templates_page.xml119
-rw-r--r--addons/website_event_meet/views/event_meeting_room_views.xml90
-rw-r--r--addons/website_event_meet/views/event_type_views.xml19
-rw-r--r--addons/website_jitsi/models/chat_room_mixin.py2
-rw-r--r--addons/website_jitsi/static/src/css/chat_room.css2
30 files changed, 1300 insertions, 4 deletions
diff --git a/addons/website_event_meet/__init__.py b/addons/website_event_meet/__init__.py
index 67dee8c60db..7d34c7c054a 100644
--- a/addons/website_event_meet/__init__.py
+++ b/addons/website_event_meet/__init__.py
@@ -1,2 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import controllers
+from . import models
diff --git a/addons/website_event_meet/__manifest__.py b/addons/website_event_meet/__manifest__.py
index 2856e3e88f9..ee97ff02819 100644
--- a/addons/website_event_meet/__manifest__.py
+++ b/addons/website_event_meet/__manifest__.py
@@ -11,11 +11,23 @@
'website': 'https://www.odoo.com/page/events',
'description': "",
'depends': [
+ # STD-TODO: remove 'website_event_track_online' we just need it to be
+ # able to use 'website.event.menu' which is defined in 'website_event_track'
+ # as well as even form view defined in track_online
'website_event_online',
+ 'website_event_track_online',
+ 'website_jitsi',
],
+ 'demo': ['data/website_event_meet_demo.xml'],
'data': [
- ],
- 'demo': [
+ 'security/ir.model.access.csv',
+ 'security/security.xml',
+ 'views/assets.xml',
+ 'views/event_meet_templates_list.xml',
+ 'views/event_meet_templates_page.xml',
+ 'views/event_meeting_room_views.xml',
+ 'views/event_event_views.xml',
+ 'views/event_type_views.xml',
],
'application': False,
'installable': True,
diff --git a/addons/website_event_meet/controllers/__init__.py b/addons/website_event_meet/controllers/__init__.py
new file mode 100644
index 00000000000..e6526380f26
--- /dev/null
+++ b/addons/website_event_meet/controllers/__init__.py
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import community
+from . import website_event_main
diff --git a/addons/website_event_meet/controllers/main.py b/addons/website_event_meet/controllers/main.py
new file mode 100644
index 00000000000..c653b9fa9f9
--- /dev/null
+++ b/addons/website_event_meet/controllers/main.py
@@ -0,0 +1,162 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import logging
+from werkzeug.exceptions import Forbidden, NotFound
+from werkzeug.utils import redirect
+
+from odoo import exceptions, http
+from odoo.http import request
+from odoo.addons.http_routing.models.ir_http import slug
+from odoo.addons.website_event_track_online.controllers.community import WebsiteEventCommunityController
+from odoo.osv import expression
+
+_logger = logging.getLogger(__name__)
+
+
+class WebsiteEventMeetController(WebsiteEventCommunityController):
+
+ def _get_event_rooms_base_domain(self, event):
+ search_domain_base = [('event_id', '=', event.id), ('is_published', '=', True)]
+ return search_domain_base
+
+ def _sort_event_rooms(self, room):
+ return (room.is_pinned, room.room_last_activity, room.id)
+
+ # ------------------------------------------------------------
+ # MAIN PAGE
+ # ------------------------------------------------------------
+
+ @http.route(["/event/<model('event.event'):event>/community"], type="http",
+ auth="public", website=True, sitemap=True)
+ def community(self, event, page=1, lang=None, **kwargs):
+ """Display the meeting rooms of the event on the frontend side.
+
+ :param event: event for which we display the meeting rooms
+ :param lang: lang id used to perform a search
+ """
+ if not event.can_access_from_current_website():
+ raise Forbidden()
+
+ return request.render(
+ "website_event_meet.event_meet",
+ self._event_meeting_rooms_get_values(event, lang=lang)
+ )
+
+ def _event_meeting_rooms_get_values(self, event, lang=None):
+ search_domain = self._get_event_rooms_base_domain(event)
+ meeting_rooms_all = request.env['event.meeting.room'].sudo().search(search_domain)
+ if lang:
+ search_domain = expression.AND([
+ search_domain,
+ [('room_lang_id', '=', int(lang))]
+ ])
+ meeting_rooms = request.env['event.meeting.room'].sudo().search(search_domain)
+ meeting_rooms = meeting_rooms.sorted(self._sort_event_rooms, reverse=True)
+
+ is_event_manager = request.env.user.has_group("event.group_event_manager")
+ if not is_event_manager:
+ meeting_rooms = meeting_rooms.filtered(lambda m: not m.room_is_full)
+
+ visitor = request.env['website.visitor']._get_visitor_from_request()
+
+ return {
+ # event information
+ "event": event.sudo(),
+ 'main_object': event,
+ # rooms
+ "meeting_rooms": meeting_rooms,
+ "current_lang": request.env["res.lang"].browse(int(lang)) if lang else False,
+ "available_languages": meeting_rooms_all.mapped("room_lang_id"),
+ "default_lang_code": request.context.get('lang', request.env.user.lang),
+ "default_username": visitor.display_name if visitor else None,
+ # environment
+ "is_event_manager": is_event_manager,
+ }
+
+ @http.route("/event/<model('event.event'):event>/meeting_room_create",
+ type="http", auth="public", methods=["POST"], website=True)
+ def create_meeting_room(self, event, **post):
+ if not event or not event.can_access_from_current_website() or not event.is_published or not event.meeting_room_allow_creation:
+ raise Forbidden()
+
+ name = post.get("name")
+ summary = post.get("summary")
+ target_audience = post.get("audience")
+ lang_code = post.get("lang_code")
+ max_capacity = post.get("capacity")
+
+ # get the record to be sure they really exist
+ lang = request.env["res.lang"].search([("code", "=", lang_code)], limit=1)
+
+ if not lang or max_capacity == "no_limit":
+ raise Forbidden()
+
+ meeting_room = request.env["event.meeting.room"].sudo().create({
+ "name": name,
+ "summary": summary,
+ "target_audience": target_audience,
+ "is_pinned": False,
+ "event_id": event.id,
+ "room_lang_id": lang.id,
+ "room_max_capacity": max_capacity,
+ "is_published": True,
+ })
+ _logger.info("New meeting room (%s) created by %s (uid %s)" % (name, request.httprequest.remote_addr, request.env.uid))
+
+ return redirect(f"/event/{slug(event)}/meeting_room/{slug(meeting_room)}")
+
+ @http.route(["/event/active_langs"], type="json", auth="public")
+ def active_langs(self):
+ return request.env["res.lang"].sudo().get_installed()
+
+ # ------------------------------------------------------------
+ # ROOM PAGE VIEW
+ # ------------------------------------------------------------
+
+ @http.route("/event/<model('event.event'):event>/meeting_room/<model('event.meeting.room'):meeting_room>",
+ type="http", auth="public", website=True, sitemap=True)
+ def event_meeting_room_page(self, event, meeting_room, **post):
+ """Display the meeting room frontend view.
+
+ :param event: Event for which we display the meeting rooms
+ :param meeting_room: Meeting Room to display
+ """
+ if not event.can_access_from_current_website() or meeting_room not in event.sudo().meeting_room_ids:
+ raise NotFound()
+
+ try:
+ meeting_room.check_access_rule('read')
+ except exceptions.AccessError:
+ raise Forbidden()
+
+ meeting_room = meeting_room.sudo()
+
+ return request.render(
+ "website_event_meet.event_meet_main",
+ self._event_meeting_room_page_get_values(event, meeting_room),
+ )
+
+ def _event_meeting_room_page_get_values(self, event, meeting_room):
+ # search for meeting room list
+ meeting_rooms_other = request.env['event.meeting.room'].sudo().search([
+ ('event_id', '=', event.id), ('id', '!=', meeting_room.id), ('is_published', '=', True),
+ ])
+
+ if not request.env.user.has_group("event.group_event_manager"):
+ # only the event manager can see meeting rooms which are full
+ meeting_rooms_other = meeting_rooms_other.filtered(lambda m: not m.room_is_full)
+
+ meeting_rooms_other = meeting_rooms_other.sorted(self._sort_event_rooms, reverse=True)
+
+ return {
+ # event information
+ 'event': event,
+ 'main_object': meeting_room,
+ 'meeting_room': meeting_room,
+ # sidebar
+ 'meeting_rooms_other': meeting_rooms_other,
+ # options
+ 'option_widescreen': True,
+ 'is_event_manager': request.env.user.has_group('event.group_event_manager'),
+ }
diff --git a/addons/website_event_meet/controllers/website_event_main.py b/addons/website_event_meet/controllers/website_event_main.py
new file mode 100644
index 00000000000..f395ca09e3f
--- /dev/null
+++ b/addons/website_event_meet/controllers/website_event_main.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from babel.dates import format_datetime
+
+from odoo import _
+from odoo.http import request
+from odoo.addons.website_event.controllers.main import WebsiteEventController
+
+
+class WebsiteEventController(WebsiteEventController):
+ def _prepare_event_register_values(self, event, **post):
+ values = super(WebsiteEventController, self)._prepare_event_register_values(event, **post)
+
+ if "from_room_id" in post and not event.is_ongoing:
+ meeting_room = request.env["event.meeting.room"].browse(int(post["from_room_id"])).sudo().exists()
+ if meeting_room and meeting_room.is_published:
+ date_begin = format_datetime(event.with_context(tz=event.date_tz).date_begin, format="medium")
+
+ values["toast_message"] = (
+ _('The event %s starts at %s (%s). \nJoin us there to chat about "%s" !')
+ % (event.name, date_begin, event.date_tz, meeting_room.name)
+ )
+
+ return values
diff --git a/addons/website_event_meet/data/website_event_meet_demo.xml b/addons/website_event_meet/data/website_event_meet_demo.xml
new file mode 100644
index 00000000000..b492f898263
--- /dev/null
+++ b/addons/website_event_meet/data/website_event_meet_demo.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <data>
+ <record id="event_meeting_room_0" model="event.meeting.room">
+ <field name="name">Best wood for furniture</field>
+ <field name="summary">Let's talk about wood types for furniture</field>
+ <field name="target_audience">wood expert(s)</field>
+ <field name="is_pinned" eval="True"/>
+ <field name="website_published" eval="True"/>
+ <field name="event_id" ref="event.event_7"/>
+ <field name="room_lang_id" ref="base.lang_en"/>
+ <field name="room_max_capacity">12</field>
+ <field name="room_participant_count">9</field>
+ </record>
+ <record id="event_meeting_room_1" model="event.meeting.room">
+ <field name="name">Reducing the ecological footprint with wood ?</field>
+ <field name="summary">Share your tips to reduce your ecological footprint using wood.</field>
+ <field name="target_audience">ecologist(s)</field>
+ <field name="is_pinned" eval="True"/>
+ <field name="website_published" eval="True"/>
+ <field name="event_id" ref="event.event_7"/>
+ <field name="room_lang_id" ref="base.lang_en"/>
+ <field name="room_max_capacity">8</field>
+ <field name="room_participant_count">8</field>
+ </record>
+ <record id="event_meeting_room_2" model="event.meeting.room">
+ <field name="name">Vos meubles préférés ?</field>
+ <field name="summary">Venez partager vos meubles préférés et l'utilisation que vous en faites.</field>
+ <field name="target_audience">client(s)</field>
+ <field name="is_pinned" eval="True"/>
+ <field name="website_published" eval="True"/>
+ <field name="event_id" ref="event.event_7"/>
+ <field name="room_lang_id" ref="base.lang_fr"/>
+ <field name="room_max_capacity">8</field>
+ <field name="room_participant_count">3</field>
+ </record>
+ <record id="event.event_7" model="event.event">
+ <field name="meeting_room_allow_creation" eval="True"/>
+ <field name="community_menu" eval="True"/>
+ </record>
+ </data>
+</odoo>
diff --git a/addons/website_event_meet/models/__init__.py b/addons/website_event_meet/models/__init__.py
new file mode 100644
index 00000000000..8f975bbef96
--- /dev/null
+++ b/addons/website_event_meet/models/__init__.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import event_event
+from . import event_type
+from . import event_meeting_room
+from . import ir_autovacuum
+from . import website_event_menu
diff --git a/addons/website_event_meet/models/event_event.py b/addons/website_event_meet/models/event_event.py
new file mode 100644
index 00000000000..4b254e91dbb
--- /dev/null
+++ b/addons/website_event_meet/models/event_event.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models, _
+from odoo.addons.http_routing.models.ir_http import slug
+
+
+class Event(models.Model):
+ _inherit = "event.event"
+
+ meeting_room_ids = fields.One2many("event.meeting.room", "event_id", string="Meeting rooms")
+ meeting_room_count = fields.Integer("Room count", compute="_compute_meeting_room_count")
+ meeting_room_allow_creation = fields.Boolean(
+ "Allow Room Creation", compute="_compute_meeting_room_allow_creation",
+ readonly=False, store=True,
+ help="Let Visitors Create Rooms")
+
+ @api.depends("meeting_room_ids")
+ def _compute_meeting_room_count(self):
+ meeting_room_count = self.env["event.meeting.room"].sudo().read_group(
+ domain=[("event_id", "in", self.ids)],
+ fields=["id:count"],
+ groupby=["event_id"],
+ )
+
+ meeting_room_count = {
+ result["event_id"][0]: result["event_id_count"]
+ for result in meeting_room_count
+ }
+
+ for event in self:
+ event.meeting_room_count = meeting_room_count.get(event.id, 0)
+
+ @api.depends("event_type_id", "community_menu", "meeting_room_allow_creation")
+ def _compute_meeting_room_allow_creation(self):
+ for event in self:
+ if event.event_type_id and event.event_type_id != event._origin.event_type_id:
+ event.meeting_room_allow_creation = event.event_type_id.meeting_room_allow_creation
+ elif event.community_menu and event.community_menu != event._origin.community_menu:
+ event.meeting_room_allow_creation = True
+ elif not event.community_menu or not event.meeting_room_allow_creation:
+ event.meeting_room_allow_creation = False
diff --git a/addons/website_event_meet/models/event_meeting_room.py b/addons/website_event_meet/models/event_meeting_room.py
new file mode 100644
index 00000000000..cad8bc78936
--- /dev/null
+++ b/addons/website_event_meet/models/event_meeting_room.py
@@ -0,0 +1,60 @@
+# -*- 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.addons.http_routing.models.ir_http import slug
+
+
+class EventMeetingRoom(models.Model):
+ _name = "event.meeting.room"
+ _description = "Event Meeting Room"
+ _order = "is_pinned DESC, id"
+ _inherit = [
+ 'chat.room.mixin',
+ 'website.published.mixin',
+ ]
+
+ name = fields.Char("Topic", required=True, translate=True)
+ active = fields.Boolean('Active', default=True)
+ 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")
+ 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()
+ for meeting_room in self:
+ if meeting_room.id:
+ base_url = meeting_room.event_id.get_base_url()
+ meeting_room.website_url = '%s/event/%s/meeting_room/%s' % (base_url, slug(meeting_room.event_id), slug(meeting_room))
+
+ @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()
+
+ return super(EventMeetingRoom, self).create(values_list)
diff --git a/addons/website_event_meet/models/event_type.py b/addons/website_event_meet/models/event_type.py
new file mode 100644
index 00000000000..4272de467b4
--- /dev/null
+++ b/addons/website_event_meet/models/event_type.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models
+
+
+class EventType(models.Model):
+ _inherit = "event.type"
+
+ meeting_room_allow_creation = fields.Boolean(
+ "Allow Room Creation", compute='_compute_meeting_room_allow_creation',
+ readonly=False, store=True,
+ help="Let Visitors Create Rooms")
+
+ @api.depends('community_menu')
+ def _compute_meeting_room_allow_creation(self):
+ for event_type in self:
+ event_type.meeting_room_allow_creation = event_type.community_menu
diff --git a/addons/website_event_meet/models/ir_autovacuum.py b/addons/website_event_meet/models/ir_autovacuum.py
new file mode 100644
index 00000000000..11907a0d5ac
--- /dev/null
+++ b/addons/website_event_meet/models/ir_autovacuum.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import datetime
+
+from odoo import api, models, fields
+
+
+class AutoVacuum(models.AbstractModel):
+ _inherit = 'ir.autovacuum'
+
+ _DELAY_CLEAN = datetime.timedelta(hours=4)
+
+ @api.model
+ def power_on(self, *args, **kwargs):
+ """Archive all non-pinned room with 0 participant if nobody has joined it for a moment."""
+ self.env["event.meeting.room"].sudo().search([
+ ("is_pinned", "=", False),
+ ("active", "=", True),
+ ("room_participant_count", "=", 0),
+ ("room_last_activity", "<", fields.Datetime.now() - self._DELAY_CLEAN),
+ ]).active = False
+
+ return super(AutoVacuum, self).power_on(*args, **kwargs)
diff --git a/addons/website_event_meet/models/website_event_menu.py b/addons/website_event_meet/models/website_event_menu.py
new file mode 100644
index 00000000000..827d3bc1fa4
--- /dev/null
+++ b/addons/website_event_meet/models/website_event_menu.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models
+
+
+class EventMenu(models.Model):
+ _inherit = "website.event.menu"
+
+ menu_type = fields.Selection(selection_add=[("meeting_room", "Event Meeting Room Menus")])
diff --git a/addons/website_event_meet/security/ir.model.access.csv b/addons/website_event_meet/security/ir.model.access.csv
new file mode 100644
index 00000000000..eeae56fb6a0
--- /dev/null
+++ b/addons/website_event_meet/security/ir.model.access.csv
@@ -0,0 +1,6 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+event_meeting_room_access_all,event.meeting.room.access.all,model_event_meeting_room,,1,0,0,0
+event_meeting_room_access_user,event.meeting.room.access.user,model_event_meeting_room,event.group_event_user,1,0,0,0
+event_meeting_room_access_manager,event.meeting.room.access.manager,model_event_meeting_room,event.group_event_manager,1,1,1,1
+chat_room_access_event_user,chat.room.access.event.user,website_jitsi.model_chat_room,event.group_event_user,1,0,0,0
+chat_room_access_event_manager,chat.room.access.event.manager,website_jitsi.model_chat_room,event.group_event_manager,1,1,1,1
diff --git a/addons/website_event_meet/security/security.xml b/addons/website_event_meet/security/security.xml
new file mode 100644
index 00000000000..e4680042533
--- /dev/null
+++ b/addons/website_event_meet/security/security.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo noupdate="1">
+
+ <record id="event_meeting_room_rule_share" model="ir.rule">
+ <field name="name">Event Meeting Room: public/portal published only</field>
+ <field name="model_id" ref="website_event_meet.model_event_meeting_room"/>
+ <field name="domain_force">[('website_published', '=', True)]</field>
+ <field name="groups" eval="[(4, ref('base.group_public')), (4, ref('base.group_portal'))]"/>
+ <field name="perm_read" eval="True"/>
+ <field name="perm_write" eval="False"/>
+ <field name="perm_create" eval="False"/>
+ <field name="perm_unlink" eval="False"/>
+ </record>
+
+</odoo>
diff --git a/addons/website_event_meet/static/src/js/customize_options.js b/addons/website_event_meet/static/src/js/customize_options.js
new file mode 100644
index 00000000000..4d8ccbd3ab7
--- /dev/null
+++ b/addons/website_event_meet/static/src/js/customize_options.js
@@ -0,0 +1,69 @@
+odoo.define('website_event_meet.set_customize_options', function (require) {
+"use strict";
+
+let EventSpecificOptions = require('website_event.set_customize_options').EventSpecificOptions;
+
+EventSpecificOptions.include({
+ xmlDependencies: (EventSpecificOptions.prototype.xmlDependencies || [])
+ .concat([
+ '/website_event_meet/static/src/xml/customize_options.xml',
+ ]),
+
+ events: _.extend({}, EventSpecificOptions.prototype.events, {
+ 'change #allow-room-creation': '_onAllowRoomCreationChange',
+ }),
+
+ start: function () {
+ this.$allowRoomCreationInput = this.$('#allow-room-creation');
+ this._super.apply(this, arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ _onAllowRoomCreationChange: function () {
+ let checkboxValue = this.$allowRoomCreationInput.is(':checked');
+ this._toggleAllowRoomCreation(checkboxValue);
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ _getCheckboxFields: function () {
+ let fields = this._super();
+ fields = _.union(fields, ['meeting_room_allow_creation']);
+ return fields;
+ },
+
+ _getCheckboxFieldMatch: function (checkboxField) {
+ if (checkboxField === 'meeting_room_allow_creation') {
+ return this.$allowRoomCreationInput;
+ }
+ return this._super(checkboxField);
+ },
+
+ _initCheckboxCallback: function (rpcData) {
+ this._super(rpcData);
+ if (rpcData[0]['meeting_room_allow_creation']) {
+ let submenuInput = this._getCheckboxFieldMatch('meeting_room_allow_creation');
+ submenuInput.attr('checked', 'checked');
+ }
+ },
+
+ _toggleAllowRoomCreation: async function (val) {
+ await this._rpc({
+ model: this.modelName,
+ method: 'write',
+ args: [[this.eventId], {
+ meeting_room_allow_creation: val
+ }],
+ });
+
+ this._reloadEventPage();
+ },
+
+});
+
+});
diff --git a/addons/website_event_meet/static/src/js/website_event_create_meeting_room_button.js b/addons/website_event_meet/static/src/js/website_event_create_meeting_room_button.js
new file mode 100644
index 00000000000..c64537a57a7
--- /dev/null
+++ b/addons/website_event_meet/static/src/js/website_event_create_meeting_room_button.js
@@ -0,0 +1,59 @@
+odoo.define('website_event_meet.website_event_create_room_button', function (require) {
+'use strict';
+
+const publicWidget = require('web.public.widget');
+const core = require('web.core');
+const QWeb = core.qweb;
+
+publicWidget.registry.websiteEventCreateMeetingRoom = publicWidget.Widget.extend({
+ selector: '.o_wevent_create_room_button',
+ xmlDependencies: ['/website_event_meet/static/src/xml/website_event_meeting_room.xml'],
+ events: {
+ 'click': '_onClickCreate',
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ _onClickCreate: async function () {
+ if (!this.$createModal) {
+ const langs = await this._rpc({
+ route: "/event/active_langs",
+ });
+
+ this.$createModal = $(QWeb.render(
+ 'event_meet_create_room_modal',
+ {
+ csrf_token: odoo.csrf_token,
+ eventId: this.$el.data("eventId"),
+ defaultLangCode: this.$el.data("defaultLangCode"),
+ langs: langs,
+ }
+ ));
+
+ this.$createModal.appendTo(this.$el.parentNode);
+ }
+
+ this.$createModal.modal('show');
+ },
+
+ //--------------------------------------------------------------------------
+ // Override
+ //--------------------------------------------------------------------------
+
+ /**
+ * Remove the create modal from the DOM, to avoid issue when editing the template
+ * with the website editor.
+ *
+ * @override
+ */
+ destroy: function () {
+ $('.o_wevent_create_meeting_room_modal').remove();
+ this._super.apply(this, arguments);
+ },
+});
+
+return publicWidget.registry.websiteEventMeetingRoom;
+
+});
diff --git a/addons/website_event_meet/static/src/js/website_event_meeting_room.js b/addons/website_event_meet/static/src/js/website_event_meeting_room.js
new file mode 100644
index 00000000000..774a186e9f7
--- /dev/null
+++ b/addons/website_event_meet/static/src/js/website_event_meeting_room.js
@@ -0,0 +1,111 @@
+odoo.define('website_event_meet.website_event_meet_meeting_room', function (require) {
+'use strict';
+
+const publicWidget = require('web.public.widget');
+const core = require('web.core');
+const Dialog = require('web.Dialog');
+const _t = core._t;
+
+publicWidget.registry.websiteEventMeetingRoom = publicWidget.Widget.extend({
+ selector: '.o_wevent_meeting_room_card',
+ xmlDependencies: ['/website_event_meet/static/src/xml/website_event_meeting_room.xml'],
+ events: {
+ 'click .o_wevent_meeting_room_delete': '_onDeleteClick',
+ 'click .o_wevent_meeting_room_duplicate': '_onDuplicateClick',
+ 'click .o_wevent_meeting_room_is_pinned': '_onPinClick',
+ },
+
+ start: function () {
+ this._super.apply(this, arguments);
+ this.meetingRoomId = parseInt(this.$el.data('meeting-room-id'));
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Delete the meeting room.
+ *
+ * @private
+ */
+ _onDeleteClick: async function (event) {
+ event.preventDefault();
+ event.stopPropagation();
+
+ Dialog.confirm(
+ this,
+ _t("Are you sure you want to close this room ?"),
+ {
+ confirm_callback: async () => {
+ await this._rpc({
+ model: 'event.meeting.room',
+ method: 'write',
+ args: [this.meetingRoomId, {is_published: false}],
+ context: this.context,
+ });
+
+ // remove the element so we do not need to refresh the page
+ this.$el.remove();
+ }
+ },
+ );
+ },
+
+ /**
+ * Duplicate the room.
+ *
+ * @private
+ */
+ _onDuplicateClick: function (event) {
+ event.preventDefault();
+ event.stopPropagation();
+ Dialog.confirm(
+ this,
+ _t("Are you sure you want to duplicate this room ?"),
+ {
+ confirm_callback: async () => {
+ await this._rpc({
+ model: 'event.meeting.room',
+ method: 'copy',
+ args: [this.meetingRoomId],
+ context: this.context,
+ });
+
+ window.location.reload();
+ }
+ },
+ );
+ },
+
+ /**
+ * Pin/unpin the room.
+ *
+ * @private
+ */
+ _onPinClick: async function (event) {
+ event.preventDefault();
+ event.stopPropagation();
+
+ const pinnedButtonClass = "o_wevent_meeting_room_pinned";
+ const isPinned = event.currentTarget.classList.contains(pinnedButtonClass);
+
+ await this._rpc({
+ model: 'event.meeting.room',
+ method: 'write',
+ args: [this.meetingRoomId, {is_pinned: !isPinned}],
+ context: this.context,
+ });
+
+ // TDE FIXME: addclass ?
+ if (isPinned) {
+ event.currentTarget.classList.remove(pinnedButtonClass);
+ } else {
+ event.currentTarget.classList.add(pinnedButtonClass);
+ }
+ }
+});
+
+return publicWidget.registry.websiteEventMeetingRoom;
+
+});
diff --git a/addons/website_event_meet/static/src/scss/event_meet_templates.scss b/addons/website_event_meet/static/src/scss/event_meet_templates.scss
new file mode 100644
index 00000000000..ddd9e030f3c
--- /dev/null
+++ b/addons/website_event_meet/static/src/scss/event_meet_templates.scss
@@ -0,0 +1,59 @@
+.o_wemeet_index {
+
+ /*
+ * COMMON
+ */
+
+ /*
+ * Meet Room PAGE
+ */
+
+ .o_wevent_online_page_container {
+
+ // Jitsi container
+ #o_wemeet_jitsi_iframe {
+ height: 85vh;
+ }
+ }
+}
+
+/*
+ * MEETING MODAL (JS / XML WIDGET)
+ */
+
+.o_wevent_create_meeting_room_modal .modal-dialog {
+ top: 25%;
+ max-width: 500px;
+ height: auto;
+}
+
+/* TDE FIXME: reorganize css cleanly */
+.o_wevent_meeting_room_is_pinned {
+ transition: 0.2s;
+
+ // the icon must be filled with white when the meeting room is not pinned
+ color: white;
+ -webkit-text-stroke: 1px $o-main-text-color;
+ -webkit-text-fill-color: transparent;
+
+ &.o_wevent_meeting_room_pinned {
+ // the icon must be filled with black when the meeting room is pinned
+ -webkit-text-fill-color: $o-main-text-color;
+ }
+}
+
+.o_wevent_meeting_room_corner_ribbon {
+ width: 200px;
+ background: #e43;
+ top: 25px;
+ left: -50px;
+ text-align: center;
+ line-height: 50px;
+ letter-spacing: 1px;
+ color: #f0f0f0;
+ transform: rotate(-45deg);
+ -webkit-transform: rotate(-45deg);
+ z-index: 1;
+ position: absolute;
+ opacity: 0.6;
+}
diff --git a/addons/website_event_meet/static/src/xml/customize_options.xml b/addons/website_event_meet/static/src/xml/customize_options.xml
new file mode 100644
index 00000000000..7be9b617bc9
--- /dev/null
+++ b/addons/website_event_meet/static/src/xml/customize_options.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<templates xml:space="preserve">
+ <t t-extend="website_event.customize_options">
+ <t t-jquery="a[name='display-website-menu']" t-operation="after">
+ <a t-if="window.document.location.pathname.endsWith('/community')" class="dropdown-item" href="#" name="allow-room-creation" role="menuitem">
+ <label class="o_switch" for="allow-room-creation">
+ <input id="allow-room-creation" type="checkbox"/>
+ <span/>
+ Allow Room Creation
+ </label>
+ </a>
+ </t>
+ </t>
+</templates>
diff --git a/addons/website_event_meet/static/src/xml/website_event_meeting_room.xml b/addons/website_event_meet/static/src/xml/website_event_meeting_room.xml
new file mode 100644
index 00000000000..bb08735c4ee
--- /dev/null
+++ b/addons/website_event_meet/static/src/xml/website_event_meeting_room.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<templates xml:space="preserve">
+ <t t-name="event_meet_create_room_modal">
+ <div class="modal fade o_wevent_create_meeting_room_modal" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content border-0">
+ <main class="modal-body p-3">
+ <form t-attf-action="/event/#{eventId}/meeting_room_create"
+ id="o_wevent_create_meeting_room_form"
+ class="text-dark"
+ method="POST">
+ <h2>Launch a new topic</h2>
+ <div class="row m-2">
+ <label class="col-4 mt-2 text-left">Room Topic</label>
+ <input class="form-control col-8" maxlength="50" name="name" placeholder="e.g. Finance" required="1"/>
+ </div>
+ <div class="row m-2">
+ <label class="col-4 mt-2 text-left">Short Summary</label>
+ <input class="form-control col-8" maxlength="200" name="summary" placeholder="e.g. Let's talk about Corporate Finance" required="1"/>
+ </div>
+ <div class="row m-2">
+ <label class="col-4 mt-2 text-left">Target People</label>
+ <input class="form-control col-8" maxlength="30" name="audience" placeholder="e.g. Accountants"/>
+ </div>
+ <div class="row m-2">
+ <label class="col-4 mt-2 text-left">Language</label>
+ <select class="o_wevent_create_meeting_room_lang form-control col-8" name="lang_code" required="1">
+ <option t-foreach="langs" t-as="language" t-att-value="language[0]" t-esc="language[1]"
+ t-att-selected="language[0] == defaultLangCode and 'selected' or None"/>
+ </select>
+ </div>
+ <div class="row m-2">
+ <label class="col-4 mt-2 text-left">Capacity</label>
+ <select class="form-control col-8" name="capacity" required="1">
+ <option value="4">4</option>
+ <option selected="selected" value="8">8</option>
+ <option value="12">12</option>
+ <option value="16">16</option>
+ <option value="20">20</option>
+ </select>
+ </div>
+ <input name="event" t-att-value="event" type="hidden"/>
+ <input name="csrf_token" t-att-value="csrf_token" type="hidden"/>
+ </form>
+ </main>
+ <footer class="modal-footer justify-content-start">
+ <button class="btn btn-primary" form="o_wevent_create_meeting_room_form" type="submit">Create</button>
+ <button class="btn bg-white text-primary" role="button" data-dismiss="modal">Discard</button>
+ </footer>
+ </div>
+ </div>
+ </div>
+ </t>
+</templates>
diff --git a/addons/website_event_meet/tests/__init__.py b/addons/website_event_meet/tests/__init__.py
new file mode 100644
index 00000000000..b2835ad5a69
--- /dev/null
+++ b/addons/website_event_meet/tests/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import test_website_event_meet
diff --git a/addons/website_event_meet/tests/test_website_event_meet.py b/addons/website_event_meet/tests/test_website_event_meet.py
new file mode 100644
index 00000000000..becb51e85bf
--- /dev/null
+++ b/addons/website_event_meet/tests/test_website_event_meet.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo.addons.event.tests.common import TestEventCommon
+from odoo.tests import Form
+
+
+class TestWebsiteEventMeet(TestEventCommon):
+ def test_meeting_room_create(self):
+ """Test that the field of the mixin are automatically filled."""
+ new_meeting_room_form = Form(self.env["event.meeting.room"])
+ new_meeting_room_form.name = "Test name"
+ new_meeting_room_form.event_id = self.event_0
+ new_meeting_room_form.target_audience = "dev"
+ new_meeting_room_form.room_max_capacity = "20"
+ meeting_room = new_meeting_room_form.save()
+
+ self.assertTrue(meeting_room.chat_room_id)
+ self.assertEqual(meeting_room.chat_room_id.max_capacity, "20")
+
+ # Try to create a meeting room without filling the related field
+ # It must create the `chat.room` with the default value
+ meeting_room = self.env["event.meeting.room"].create({
+ "name": "Test 2",
+ "event_id": self.event_0.id,
+ "target_audience": "dev"
+ })
+
+ self.assertTrue(meeting_room.chat_room_id)
+ self.assertTrue(meeting_room.chat_room_id.name)
+ self.assertEqual(meeting_room.chat_room_id.max_capacity, "8")
+
+ def test_meeting_room_copy(self):
+ """Test the duplication of the meeting room."""
+ meeting_room_1 = self.env["event.meeting.room"].create({
+ "name": "Test meeting room",
+ "event_id": self.event_0.id,
+ "target_audience": "dev",
+ "room_max_capacity": "20",
+ })
+
+ meeting_room_2 = meeting_room_1.copy()
+
+ chat_room_1 = meeting_room_1.chat_room_id
+ chat_room_2 = meeting_room_2.chat_room_id
+
+ self.assertTrue(chat_room_1)
+ self.assertTrue(chat_room_2)
+ self.assertNotEqual(chat_room_1.id, chat_room_2.id, "Must create a new chat room")
+ self.assertNotEqual(chat_room_1.name, chat_room_2.name, "Must generate a new chat room name")
+ 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")
+
+ def test_meeting_room_unlink(self):
+ """Test the duplication of the meeting room."""
+ meeting_room = self.env["event.meeting.room"].create({
+ "name": "Test meeting room",
+ "event_id": self.event_0.id,
+ "target_audience": "dev",
+ "room_max_capacity": "20",
+ })
+
+ self.assertTrue(meeting_room.chat_room_id)
+ chat_room_id = meeting_room.chat_room_id.id
+
+ meeting_room.unlink()
+ self.assertFalse(self.env["chat.room"].browse(chat_room_id).exists())
diff --git a/addons/website_event_meet/views/assets.xml b/addons/website_event_meet/views/assets.xml
new file mode 100644
index 00000000000..0d57557fb4d
--- /dev/null
+++ b/addons/website_event_meet/views/assets.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <template id="assets_frontend" inherit_id="website.assets_frontend" name="Event Meet Assets">
+ <xpath expr="." position="inside">
+ <link href="/website_event_meet/static/src/scss/event_meet_templates.scss" rel="stylesheet" type="text/scss"/>
+ <script src="/website_event_meet/static/src/js/customize_options.js" type="text/javascript"/>
+ <script src="/website_event_meet/static/src/js/website_event_meeting_room.js" type="text/javascript"/>
+ <script src="/website_event_meet/static/src/js/website_event_create_meeting_room_button.js" type="text/javascript"/>
+ </xpath>
+ </template>
+</odoo>
diff --git a/addons/website_event_meet/views/event_event_views.xml b/addons/website_event_meet/views/event_event_views.xml
new file mode 100644
index 00000000000..08d5c46e86b
--- /dev/null
+++ b/addons/website_event_meet/views/event_event_views.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo><data>
+
+ <record id="event_event_view_form" model="ir.ui.view">
+ <field name="name">event.event.view.form.inherit.meet</field>
+ <field name="model">event.event</field>
+ <field name="inherit_id" ref="website_event_track_online.event_event_view_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='is_published']" position="before">
+ <button class="oe_stat_button" context="{'default_event_id': active_id, 'search_default_event_id': active_id}" icon="fa-comments-o" name="%(event_meeting_room_action)d" type="action">
+ <field name="meeting_room_count" string="Rooms" widget="statinfo"/>
+ </button>
+ </xpath>
+ <xpath expr="//field[@name='community_menu']" position="after">
+ <field name="meeting_room_allow_creation" invisible="1"/>
+ </xpath>
+ </field>
+ </record>
+
+</data></odoo>
+
diff --git a/addons/website_event_meet/views/event_meet_templates_list.xml b/addons/website_event_meet/views/event_meet_templates_list.xml
new file mode 100644
index 00000000000..85b92c920fe
--- /dev/null
+++ b/addons/website_event_meet/views/event_meet_templates_list.xml
@@ -0,0 +1,167 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+<template id="event_meet" name="Meeting Rooms">
+ <t t-call="website_event.layout">
+ <div class="o_wevent_online o_wevent_online_bg o_wemeet_index">
+ <!-- Drag/Drop Area -->
+ <div id="oe_structure_website_event_location_1" class="oe_structure"/>
+ <!-- Content -->
+ <div class="o_wemeet_container container">
+ <div class="row mb-5 mx-0">
+ <t t-call="website_event_meet.community_main"/>
+ <t t-call="website_event_meet.community_aside"/>
+ </div>
+ </div>
+ <!-- Drag/Drop Area -->
+ <div id="oe_structure_website_event_location_2" class="oe_structure"/>
+ </div>
+ </t>
+</template>
+
+<!-- ============================================================ -->
+<!-- CONTENT: MAIN TEMPLATES -->
+<!-- ============================================================ -->
+
+<!-- Meeting Rooms Main Display -->
+<template id="community_main" name="Meeting Rooms: Main Display">
+ <div class="col-12 col-md-8 pl-0 pr-0 pr-md-3 mt-3">
+ <h1 class="d-flex flex-row justify-content-between">
+ <span>Join a room</span>
+ <div class="dropdown">
+ <a class="dropdown-toggle o-no-caret btn pr-0" title="Languages Menu"
+ aria-label="Dropdown menu" data-display="static" data-toggle="dropdown" href="#" role="button">
+ <span t-esc="current_lang.name if current_lang else 'All Languages'"/> ▼</a>
+ <div class="dropdown-menu" role="menu">
+ <a class="dropdown-item" role="menuitem" t-attf-href="/event/#{slug(event)}/community">All Languages
+ </a>
+ <a class="dropdown-item" role="menuitem" t-as="language" t-attf-href="/event/#{slug(event)}/community?lang=#{language.id}" t-esc="language.name" t-foreach="available_languages"/>
+ </div>
+ </div>
+ </h1>
+ <hr class="mt-2 mb-3"/>
+ <p class="mt-">Choose a topic that interests you and start talking with the community. <br/> Don't forget to setup your camera and microphone.</p>
+ <div class="d-flex flex-column justify-content-start align-items-start">
+ <t t-as="meeting_room" t-call="website_event_meet.meeting_room_card" t-foreach="meeting_rooms">
+ <t t-set="meeting_room" t-value="meeting_room"/>
+ <t t-set="opened" t-value="int(meeting_room.id == open_room_id)"/>
+ </t>
+ <div t-if="not meeting_rooms" class="m-auto text-center text-muted">
+ <h3 class="mt8">No Room Open</h3>
+ <p>Create one to get conversations going</p>
+ </div>
+ </div>
+ </div>
+</template>
+
+<template id="meeting_room_card" name="Meeting Room Card">
+ <div class="modal o_join_later_modal" t-attf-id="o_join_later_modal_#{meeting_room.id}" tabindex="-1" role="dialog" style="height: 400px; top:calc(50% - 200px);">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="mt-4 col-12 alert alert-warning text-center" role="alert">
+ <nav class="navbar navbar-default">
+ <div class="container-fluid">
+ <div class="navbar-header">
+ <div class="o_wevent_meeting_room_card_menu"></div>
+ </div>
+ </div>
+ </nav>
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">&amp;times;</button>
+ <span>This room is not open right now!</span><br />
+ Join us here on the
+ <strong itemprop="startDate" t-field="event.with_context(tz=event.date_tz).date_begin" t-options="{'format': 'medium'}"/>
+ to have a chat with us!
+ </div>
+
+ <div class="modal-body row">
+ <div class="col-3">
+ <div class="w-100" t-attf-style="background-image: #{json.loads(event.cover_properties).get('background-image')}; min-height: 5rem; background-size: cover;"/>
+ </div>
+ <div class="col">
+ <h5 t-esc="meeting_room.name"/>
+ <div class="text-muted mb-2"><i class="fa fa-globe"/> <span t-esc="meeting_room.room_lang_id.name"/></div>
+ <span t-if="meeting_room.summary" t-raw="meeting_room.summary"/>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- Action to do when clicking on the card -->
+ <t t-if="event.is_ongoing or is_event_manager">
+ <!--During the event or if event manager-->
+ <t t-set="meeting_room_href" t-value="'/event/' + slug(event) + '/meeting_room/' + slug(meeting_room)"/>
+ </t>
+ <t t-elif="not event.is_ongoing and not event.is_participating">
+ <!--Pre-event, if not registered yet-->
+ <t t-set="meeting_room_href" t-value="'/event/' + slug(event) + '/register?from_room_id=%i' % meeting_room.id"/>
+ </t>
+ <t t-else="">
+ <!--Pre-event, if registered but event not stared yet-->
+ <t t-set="meeting_room_href" t-value="'#'"/>
+ <t t-set="meeting_room_data_toggle" t-value="'modal'"/>
+ <t t-set="meeting_room_data_target" t-value="'#o_join_later_modal_%i' % meeting_room.id"/>
+ </t>
+
+ <a t-if="is_event_manager or not meeting_room.room_is_full"
+ t-att-data-meeting-room-id="meeting_room.id"
+ t-att-data-open-room="opened"
+ t-att-data-is-event-manager="int(is_event_manager)"
+ t-attf-class="card o_wevent_meeting_room_card w-100 my-2 bg-light d-block text-decoration-none"
+ t-att-href="meeting_room_href"
+ t-att-data-toggle="meeting_room_data_toggle"
+ t-att-data-target="meeting_room_data_target">
+ <div class="text-decoration-none w-100 h-100 p-3">
+ <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"/>
+ <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">&#8942;</h3></button>
+ <div class="dropdown-menu">
+ <div class="dropdown-item font-weight-bold disabled">Room Manager</div>
+ <button class="dropdown-item btn btn-danger o_wevent_meeting_room_duplicate" type="button">Duplicate</button>
+ <button class="dropdown-item btn btn-danger o_wevent_meeting_room_delete" type="button">Close</button>
+ </div>
+ </div>
+ <button t-attf-class="o_wevent_meeting_room_is_pinned float-right btn #{'o_wevent_meeting_room_pinned' if meeting_room.is_pinned else ''}">
+ <i class="fa fa-thumb-tack"/>
+ </button>
+ </div>
+ </div>
+ <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"/>&amp;nbsp;</span>
+ <t t-esc="meeting_room.target_audience or 'participant(s)'"/>
+ </span>
+ <div class="d-inline border py-1 px-2 bg-secondary" t-esc="meeting_room.room_lang_id.name"/>
+ </div>
+ </div>
+ </div>
+ </a>
+</template>
+
+<!-- ============================================================ -->
+<!-- ASIDE: CREATE A ROOM -->
+<!-- ============================================================ -->
+
+<template id="community_aside" name="Community: Aside">
+ <div class="col-md-4 p-0 mt-3 o_wevent_community_aside">
+ <div class="d-none d-md-block mb-3" t-if="event.meeting_room_allow_creation">
+ <h1>Launch a new topic</h1>
+ <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"
+ t-att-data-event-id="event.id"
+ t-att-data-default-lang-code="default_lang_code">
+ <span>Create a Room</span>
+ </a>
+ </div>
+ </div>
+</template>
+
+</odoo>
diff --git a/addons/website_event_meet/views/event_meet_templates_page.xml b/addons/website_event_meet/views/event_meet_templates_page.xml
new file mode 100644
index 00000000000..36cecd3e6a9
--- /dev/null
+++ b/addons/website_event_meet/views/event_meet_templates_page.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+<template id="event_meet_main" name="Event Meet">
+ <t t-set="no_header" t-value="option_widescreen"/>
+ <t t-set="no_footer" t-value="option_widescreen"/>
+ <t t-call="website_event.layout">
+ <div class="o_wevent_online o_wevent_online_bg o_wemeet_index">
+ <!-- Options -->
+ <t t-set="option_widescreen" t-value="option_widescreen or False"/>
+ <!-- Drag/Drop Area -->
+ <div id="oe_structure_wemeet_index_1" class="oe_structure"/>
+ <!-- Content -->
+ <div t-att-class="'o_wevent_online_page_container %s' % ('container pb-3' if not option_widescreen else 'pb-3')">
+ <div class="row mb-5 mx-0">
+ <t t-call="website_event_meet.meeting_room_aside"/>
+ <t t-call="website_event_meet.meeting_room_main"/>
+ </div>
+ </div>
+ <!-- Drag/Drop Area -->
+ <div id="oe_structure_wemeet_index_2" class="oe_structure"/>
+ </div>
+ </t>
+</template>
+
+<!-- ============================================================ -->
+<!-- CONTENT: MAIN TEMPLATES -->
+<!-- ============================================================ -->
+
+<template id="meeting_room_main" name="Meeting Room: Main Content">
+ <div t-att-class="'col-12 o_wevent_online_page_main o_wemeet_room_main bg-white p-0 %s' % ('col-md-9 col-lg-10' if option_widescreen else 'col-md-8 col-lg-9')">
+ <!-- EVENT NOT STARTED ALERTS -->
+ <div t-if="not meeting_room.event_id.is_ongoing" class="alert alert-warning text-center">
+ The event <span t-esc="meeting_room.event_id.name" class="font-weight-bold"/>
+ <span t-if="meeting_room.event_id.start_today">
+ starts in
+ <span t-esc="meeting_room.event_id.start_remaining"
+ t-options="{'widget': 'duration', 'digital': True, 'unit': 'minute', 'round': 'minute'}"/>.
+ </span>
+ <span class="my-0" t-else="meeting_room.event_id.start_today">
+ starts at
+ <span t-field="meeting_room.event_id.with_context(tz=meeting_room.event_id.date_tz).date_begin"
+ 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>
+ </div>
+ <!-- ROOM CONTENT -->
+ <div class="d-flex flex-column">
+ <div t-if="meeting_room.room_is_full and not is_event_manager" class="alert alert-warning text-center">
+ <span>Oops! This room is full !</span><br />Come back later to have a chat with us!
+ </div>
+ <div t-else="" class="d-flex flex-column">
+ <div id="o_wemeet_jitsi_iframe">
+ <div class="o_wjitsi_chat_room_loading position-absolute w-100 text-center text-muted">
+ <i class="fa fa-spin fa-spinner mr-3"/>
+ <span>Loading your room...</span>
+ </div>
+ </div>
+ <div class="d-flex flex-row-reverse">
+ <t t-call="website_jitsi.chat_room_join_button">
+ <t t-set="_classes" t-value="'d-none'"/>
+ <t t-set="room_name" t-value="meeting_room.room_name"/>
+ <t t-set="chat_room_id" t-value="meeting_room.chat_room_id.id"/>
+ <t t-set="auto_open" t-value="1"/>
+ <t t-set="attach_to" t-value="'#o_wemeet_jitsi_iframe'"/>
+ <t t-set="check_full" t-value="is_event_manager"/>
+ <t t-set="default_username" t-value="default_username"/>
+ </t>
+ </div>
+ </div>
+ </div>
+ <!-- ROOM DESCRIPTION -->
+ <div class="mx-3">
+ <span class="h5 d-inline-block mt-3 mb-1" t-esc="meeting_room.name"/>
+ <br/>
+ <span class="text-muted">A chat among <span class="font-weight-bold" t-esc="meeting_room.target_audience"/></span>
+ </div>
+ <hr class="mt-2 mb-0"/>
+ <div t-field="meeting_room.summary" class="my-2 mx-3 oe_no_empty"/>
+ </div>
+</template>
+
+<!-- ============================================================ -->
+<!-- ASIDE: CONTROL PANEL -->
+<!-- ============================================================ -->
+
+<template id="meeting_room_aside" name="Meeting Room: Aside">
+ <div t-att-class="'col-12 pl-0 pr-0 pr-md-3 o_wevent_online_page_aside %s' % ('col-md-3 col-lg-2' if option_widescreen else 'col-md-4 col-lg-3')">
+ <div class="bg-white o_wevent_online_page_aside_content">
+ <div class="d-flex align-items-center justify-content-between mx-2 py-1">
+ <span class="h5 mb-0 pt-0 pt-md-3 pb-0 pb-md-2">Other Rooms</span>
+ <a href="#collapse_meet_room_aside" data-toggle="collapse" class="d-lg-none p-2 text-decoration-none o_wevent_online_page_aside_collapse collapsed">
+ <i class="fa fa-chevron-down d-lg-none"/>
+ </a>
+ </div>
+ <ul id="collapse_meet_room_aside" class="list-unstyled collapse d-lg-block mb-0">
+ <li t-foreach="meeting_rooms_other" t-as="meeting_room_other">
+ <a class="d-block w-100 h-100 px-2 pt-2 pb-1 text-decoration-none"
+ t-att-href="'/event/%s/meeting_room/%s' % (slug(event), slug(meeting_room_other))">
+ <div class="flex-grow-1 mw-100">
+ <div class="text-truncate" t-esc="meeting_room_other.name"/>
+ <span class="text-muted" t-esc="meeting_room_other.summary"></span>
+ <div class="d-flex justify-content-between align-items-center">
+ <div class="text-muted">
+ <b>&amp;#9900;&amp;nbsp;</b>
+ <small><t t-esc="meeting_room_other.room_participant_count"/> <t t-esc="meeting_room_other.target_audience"/></small>
+ </div>
+ <small t-if="meeting_room_other.room_lang_id" class="text-muted"><i class="fa fa-globe"/> <t t-esc="meeting_room_other.room_lang_id.name"/></small>
+ </div>
+ </div>
+ </a>
+ </li>
+ </ul>
+ </div>
+ </div>
+</template>
+
+</odoo>
diff --git a/addons/website_event_meet/views/event_meeting_room_views.xml b/addons/website_event_meet/views/event_meeting_room_views.xml
new file mode 100644
index 00000000000..9012ee443bd
--- /dev/null
+++ b/addons/website_event_meet/views/event_meeting_room_views.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0"?>
+<odoo>
+ <record id="website_event_meet.event_meeting_room_action" model="ir.actions.act_window">
+ <field name="name">Meeting Room</field>
+ <field name="res_model">event.meeting.room</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <record id="event_meeting_room_view_search" model="ir.ui.view">
+ <field name="name">event.meeting.room.search</field>
+ <field name="model">event.meeting.room</field>
+ <field name="arch" type="xml">
+ <search string="Meeting Room">
+ <field name="event_id"/>
+ </search>
+ </field>
+ </record>
+ <record id="event_meeting_room_view_form" model="ir.ui.view">
+ <field name="name">event.meeting.room.form</field>
+ <field name="model">event.meeting.room</field>
+ <field name="arch" type="xml">
+ <form string="Meeting Room">
+ <sheet>
+ <div class="oe_button_box" name="button_box">
+ <field name="website_url" invisible="1"/>
+ <field name="is_published" widget="website_redirect_button"/>
+ </div>
+ <label for="name"/>
+ <h1>
+ <field name="name" placeholder="e.g. Finance"/>
+ </h1>
+ <group>
+ <group>
+ <field name="event_id"/>
+ <field name="summary" placeholder="e.g. Let's talk about Corporate Finance"/>
+ <field name="target_audience" placeholder="e.g. Accountants"/>
+ <field name="is_pinned"/>
+ </group>
+ <group>
+ <field name="chat_room_id"/>
+ <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}"/>
+ </group>
+ </group>
+ <notebook>
+ <page name="Reporting" string="Reporting">
+ <group>
+ <field name="room_last_activity"/>
+ <field name="room_max_participant_reached"/>
+ </group>
+ </page>
+ </notebook>
+ </sheet>
+ </form>
+ </field>
+ </record>
+ <record id="event_meeting_room_view_tree" model="ir.ui.view">
+ <field name="name">event.meeting.room.tree</field>
+ <field name="model">event.meeting.room</field>
+ <field name="arch" type="xml">
+ <tree string="Meeting Room">
+ <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_max_capacity"/>
+ <field name="room_lang_id"/>
+ </tree>
+ </field>
+ </record>
+ <record id="event_meeting_room_view_search" model="ir.ui.view">
+ <field name="name">event.meeting.room.search</field>
+ <field name="model">event.meeting.room</field>
+ <field name="arch" type="xml">
+ <search>
+ <field name="name" string="Topic"/>
+ <field name="summary" string="Summary"/>
+ <field name="target_audience" string="Audience"/>
+ <field name="event_id" string="Event"/>
+ <filter domain="[('is_published', '=', False)]" name="filter_unpublished" string="Unpublished"/>
+ <group expand="0" string="Group By">
+ <filter string="Event" name="groupby_event" domain="[]" context="{'group_by': 'event_id'}"/>
+ </group>
+ </search>
+ </field>
+ </record>
+</odoo>
diff --git a/addons/website_event_meet/views/event_type_views.xml b/addons/website_event_meet/views/event_type_views.xml
new file mode 100644
index 00000000000..8e76e97f4d6
--- /dev/null
+++ b/addons/website_event_meet/views/event_type_views.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<odoo><data>
+ <record id="event_type_view_form" model="ir.ui.view">
+ <field name="name">event.type.view.form.inherit.meet</field>
+ <field name="model">event.type</field>
+ <field name="inherit_id" ref="website_event.event_type_view_form_inherit_website"/>
+ <field name="arch" type="xml">
+ <xpath expr="//div[@name='website_menu']" position='after'>
+ <div class="row mt16" attrs="{'invisible': [('website_menu', '=', False)]}">
+ <label class="col-lg-4" for="meeting_room_allow_creation"/> <field name="meeting_room_allow_creation"/>
+ </div>
+ </xpath>
+ <xpath expr="//div[@id='community-menu']" position="attributes">
+ <attribute name="groups"></attribute>
+ </xpath>
+ </field>
+ </record>
+
+</data></odoo>
diff --git a/addons/website_jitsi/models/chat_room_mixin.py b/addons/website_jitsi/models/chat_room_mixin.py
index 6aedb60bc6f..f5071b13d5e 100644
--- a/addons/website_jitsi/models/chat_room_mixin.py
+++ b/addons/website_jitsi/models/chat_room_mixin.py
@@ -49,7 +49,7 @@ class ChatRoomMixin(models.AbstractModel):
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().copy(default)
+ return super(ChatRoomMixin, self).copy(default)
def unlink(self):
if self.chat_room_id:
diff --git a/addons/website_jitsi/static/src/css/chat_room.css b/addons/website_jitsi/static/src/css/chat_room.css
index ad0b2b8b0d5..a51f80b6177 100644
--- a/addons/website_jitsi/static/src/css/chat_room.css
+++ b/addons/website_jitsi/static/src/css/chat_room.css
@@ -13,5 +13,5 @@
}
.o_wjitsi_chat_room_loading {
- top: 50%;
+ top: 40%;
}