[REM] website_event_track_session: merge into website_event_track

PURPOSE

Clean organization and models linked to Event Online feature introduced in
semi stable saas-13.3 at odoo/odoo@981f95bbf4 and odoo/enterprise@14722028ae

Also clean website event menu not being available in website event but only
in website event track, which implies some extra-code to manage it.

In short: merge website_event_online in website_event, website_event_track
_(online/session) in website_event_track.

RATIONALE

_online modules have been added to extend content of website_event and
website_event_track without having any impact on those module. First step
of cleaning is to move this content directly in base module.

track_session module is mainly a rewrite of track module. Second step of
cleaning is to move its content directly in website_event_track.

SPECIFICATIONS

Move all content of website event track session to website event tracK. It
was mainly of rewriting of views and can now be safely merged into the base
track module.

Update dependencies accordingly.

LINKS

Task ID-2319779
COM odoo/odoo#56067
ENT odoo/enterprise#12520
UPG odoo/upgrade#1654
This commit is contained in:
Thibault Delavallée
2020-08-21 17:30:13 +00:00
parent 1163013ed6
commit de8a5652b2
34 changed files with 907 additions and 1292 deletions
-1
View File
@@ -21,7 +21,6 @@ automatic lead generation, full Online support, ...
'website_event_sale',
'website_event_track',
'website_event_track_exhibitor',
'website_event_track_session',
'website_event_track_live',
'website_event_track_quiz',
],
@@ -1,7 +1,9 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from ast import literal_eval
from datetime import timedelta
from pytz import timezone, utc
from werkzeug.exceptions import Forbidden, NotFound
import babel
@@ -9,13 +11,28 @@ import babel.dates
import base64
import pytz
from odoo import exceptions, http, fields
from odoo import exceptions, http, fields, _
from odoo.http import request
from odoo.tools import plaintext2html, html2plaintext
from odoo.osv import expression
from odoo.tools import is_html_empty, plaintext2html
class EventTrackController(http.Controller):
def _get_event_tracks_base_domain(self, event):
""" Base domain for displaying tracks. Restrict to accepted or published
tracks for people not managing events. Unpublished tracks may be displayed
but not reachable for teasing purpose. """
search_domain_base = [
('event_id', '=', event.id),
]
if not request.env.user.has_group('event.group_event_user'):
search_domain_base = expression.AND([
search_domain_base,
['|', ('is_published', '=', True), ('is_accepted', '=', True)]
])
return search_domain_base
# ------------------------------------------------------------
# TRACK LIST VIEW
# ------------------------------------------------------------
@@ -24,27 +41,110 @@ class EventTrackController(http.Controller):
'''/event/<model("event.event"):event>/track''',
'''/event/<model("event.event"):event>/track/tag/<model("event.track.tag"):tag>'''
], type='http', auth="public", website=True, sitemap=False)
def event_tracks(self, event, tag=None, **post):
if not event.can_access_from_current_website() or (tag and tag.color == 0):
def event_tracks(self, event, tag=None, **searches):
""" Main route
:param event: event whose tracks are about to be displayed;
:param tag: deprecated: search for a specific tag
:param searches: frontend search dict, containing
* 'search': search string;
* 'tags': list of tag IDs for filtering;
"""
if not event.can_access_from_current_website():
raise NotFound()
event = event.with_context(tz=event.date_tz or 'UTC')
searches = {}
if tag:
searches.update(tag=tag.id)
tracks = event.track_ids.filtered(lambda track: tag in track.tag_ids)
else:
tracks = event.track_ids
return request.render(
"website_event_track.tracks_session",
self._event_tracks_get_values(event, tag=tag, **searches)
)
values = {
def _event_tracks_get_values(self, event, tag=None, **searches):
# init and process search terms
searches.setdefault('search', '')
searches.setdefault('search_wishlist', '')
searches.setdefault('tags', '')
search_domain = self._get_event_tracks_base_domain(event)
# search on content
if searches.get('search'):
search_domain = expression.AND([
search_domain,
[('name', 'ilike', searches['search'])]
])
# search on tags
search_tags = self._get_search_tags(searches['tags'])
if not search_tags and tag: # backward compatibility
search_tags = tag
if search_tags:
# Example: You filter on age: 10-12 and activity: football.
# Doing it this way allows to only get events who are tagged "age: 10-12" AND "activity: football".
# Add another tag "age: 12-15" to the search and it would fetch the ones who are tagged:
# ("age: 10-12" OR "age: 12-15") AND "activity: football
grouped_tags = dict()
for search_tag in search_tags:
grouped_tags.setdefault(search_tag.category_id, list()).append(search_tag)
search_domain_items = [
[('tag_ids', 'in', [tag.id for tag in grouped_tags[group]])]
for group in grouped_tags
]
search_domain = expression.AND([
search_domain,
*search_domain_items
])
# fetch data to display with TZ set for both event and tracks
now_tz = utc.localize(fields.Datetime.now().replace(microsecond=0), is_dst=False).astimezone(timezone(event.date_tz))
today_tz = now_tz.date()
event = event.with_context(tz=event.date_tz or 'UTC')
tracks_sudo = event.env['event.track'].sudo().search(search_domain, order='date asc')
tag_categories = request.env['event.track.tag.category'].sudo().search([])
# filter on wishlist (as post processing due to costly search on is_reminder_on)
if searches.get('search_wishlist'):
tracks_sudo = tracks_sudo.filtered(lambda track: track.is_reminder_on)
# organize categories for display: announced, live, soon and day-based
tracks_announced = tracks_sudo.filtered(lambda track: not track.date)
tracks_wdate = tracks_sudo - tracks_announced
date_begin_tz_all = list(set(
dt.date()
for dt in self._get_dt_in_event_tz(tracks_wdate.mapped('date'), event)
))
date_begin_tz_all.sort()
tracks_sudo_live = tracks_wdate.filtered(lambda track: track.is_published and track.is_track_live)
tracks_sudo_soon = tracks_wdate.filtered(lambda track: track.is_published and not track.is_track_live and track.is_track_soon)
tracks_by_day = []
for display_date in date_begin_tz_all:
matching_tracks = tracks_wdate.filtered(lambda track: self._get_dt_in_event_tz([track.date], event)[0].date() == display_date)
tracks_by_day.append({'date': display_date, 'name': display_date, 'tracks': matching_tracks})
if tracks_announced:
tracks_announced = tracks_announced.sorted('wishlisted_by_default', reverse=True)
tracks_by_day.append({'date': False, 'name': _('Coming soon'), 'tracks': tracks_announced})
# return rendering values
return {
# event information
'event': event,
'main_object': event,
'tracks': tracks,
'tags': event.tracks_tag_ids,
# tracks display information
'tracks': tracks_sudo,
'tracks_by_day': tracks_by_day,
'tracks_live': tracks_sudo_live,
'tracks_soon': tracks_sudo_soon,
'today_tz': today_tz,
# search information
'searches': searches,
'html2plaintext': html2plaintext
'search_key': searches['search'],
'search_wishlist': searches['search_wishlist'],
'search_tags': search_tags,
'tag_categories': tag_categories,
# environment
'is_html_empty': is_html_empty,
'hostname': request.httprequest.host.split(':')[0],
'user_event_manager': request.env.user.has_group('event.group_event_manager'),
}
return request.render("website_event_track.tracks", values)
# ------------------------------------------------------------
# AGENDA VIEW
@@ -219,14 +319,41 @@ class EventTrackController(http.Controller):
# TRACK PAGE VIEW
# ------------------------------------------------------------
@http.route(['''/event/<model("event.event"):event>/track/<model("event.track", "[('event_id','=',event.id)]"):track>'''], type='http', auth="public", website=True, sitemap=True)
def event_track_view(self, event, track, **post):
if not event.can_access_from_current_website():
raise NotFound()
@http.route('''/event/<model("event.event", "[('website_track', '=', True)]"):event>/track/<model("event.track", "[('event_id', '=', event.id)]"):track>''',
type='http', auth="public", website=True, sitemap=True)
def event_track_page(self, event, track, **options):
track = self._fetch_track(track.id, allow_is_accepted=False)
track = track.sudo().with_context(tz=event.date_tz or 'UTC')
values = {'track': track, 'event': track.event_id, 'main_object': track}
return request.render("website_event_track.track_view", values)
return request.render(
"website_event_track.event_track_main",
self._event_track_page_get_values(event, track.sudo(), **options)
)
def _event_track_page_get_values(self, event, track, **options):
track = track.sudo()
option_widescreen = options.get('widescreen', False)
option_widescreen = bool(option_widescreen) if option_widescreen != '0' else False
# search for tracks list
tracks_other = track._get_track_suggestions(
restrict_domain=self._get_event_tracks_base_domain(track.event_id),
limit=10
)
return {
# event information
'event': event,
'main_object': track,
'track': track,
# sidebar
'tracks_other': tracks_other,
# options
'option_widescreen': option_widescreen,
# environment
'is_html_empty': is_html_empty,
'hostname': request.httprequest.host.split(':')[0],
'user_event_manager': request.env.user.has_group('event.group_event_manager'),
}
@http.route("/event/track/toggle_reminder", type="json", auth="public", website=True)
def track_reminder_toggle(self, track_id, set_reminder_on):
@@ -330,3 +457,21 @@ class EventTrackController(http.Controller):
raise Forbidden()
return track
def _get_search_tags(self, tag_search):
# TDE FIXME: make me generic (slides, event, ...)
try:
tag_ids = literal_eval(tag_search)
except Exception:
tags = request.env['event.track.tag'].sudo()
else:
# perform a search to filter on existing / valid tags implicitly
tags = request.env['event.track.tag'].sudo().search([('id', 'in', tag_ids)])
return tags
def _get_dt_in_event_tz(self, datetimes, event):
tz_name = event.date_tz
return [
utc.localize(dt, is_dst=False).astimezone(timezone(tz_name))
for dt in datetimes
]
@@ -344,6 +344,10 @@
<field name="duration">2</field>
<field name="user_id" ref="base.user_admin"/>
<field name="partner_id" ref="base.res_partner_address_15"/>
<field name="website_cta" eval="True"/>
<field name="website_cta_title">Try Now</field>
<field name="website_cta_url">http://www.example.com</field>
<field name="website_cta_delay">10</field>
</record>
<record id="event_7_track_2" model="event.track">
<field name="name">First Day Wrapup</field>
@@ -387,6 +391,10 @@
<field name="duration">2</field>
<field name="user_id" ref="base.user_admin"/>
<field name="partner_id" ref="base.res_partner_address_15"/>
<field name="website_cta" eval="True"/>
<field name="website_cta_title">Try Now</field>
<field name="website_cta_url">http://www.example.com</field>
<field name="website_cta_delay">10</field>
</record>
<!-- Location 2 -->
<record id="event_7_track_5" model="event.track">
@@ -2,6 +2,8 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import timedelta
from pytz import utc
from random import randint
from odoo import api, fields, models
from odoo.addons.http_routing.models.ir_http import slug
@@ -78,6 +80,27 @@ class Track(models.Model):
date = fields.Datetime('Track Date')
date_end = fields.Datetime('Track End Date', compute='_compute_end_date', store=True)
duration = fields.Float('Duration', default=1.5, help="Track duration in hours.")
is_track_live = fields.Boolean(
'Is Track Live', compute='_compute_track_time_data',
help="Track has started and is ongoing")
is_track_soon = fields.Boolean(
'Is Track Soon', compute='_compute_track_time_data',
help="Track begins soon")
is_track_today = fields.Boolean(
'Is Track Today', compute='_compute_track_time_data',
help="Track begins today")
is_track_upcoming = fields.Boolean(
'Is Track Upcoming', compute='_compute_track_time_data',
help="Track is not yet started")
is_track_done = fields.Boolean(
'Is Track Done', compute='_compute_track_time_data',
help="Track is finished")
track_start_remaining = fields.Integer(
'Minutes before track starts', compute='_compute_track_time_data',
help="Remaining time before track starts (seconds)")
track_start_relative = fields.Integer(
'Minutes compare to track start', compute='_compute_track_time_data',
help="Relative time compared to track start (seconds)")
# frontend description
website_image = fields.Image(string="Website Image", max_width=1024, max_height=1024)
website_image_url = fields.Char(
@@ -100,6 +123,18 @@ class Track(models.Model):
wishlisted_by_default = fields.Boolean(
string='Always Wishlisted',
help="""If set, the talk will be starred for each attendee registered to the event. The attendee won't be able to un-star this talk.""")
# Call to action
website_cta = fields.Boolean('Magic Button')
website_cta_title = fields.Char('Button Title')
website_cta_url = fields.Char('Button Target URL')
website_cta_delay = fields.Integer('Button appears')
# time information for CTA
is_website_cta_live = fields.Boolean(
'Is CTA Live', compute='_compute_cta_time_data',
help="CTA button is available")
website_cta_start_remaining = fields.Integer(
'Minutes before CTA starts', compute='_compute_cta_time_data',
help="Remaining time before CTA starts (seconds)")
@api.depends('name')
def _compute_website_url(self):
@@ -219,12 +254,61 @@ class Track(models.Model):
])
return [('id', 'in', track_visitors.track_id.ids)]
# TIME
@api.depends('date', 'date_end')
def _compute_track_time_data(self):
""" Compute start and remaining time for track itself. Do everything in
UTC as we compute only time deltas here. """
now_utc = utc.localize(fields.Datetime.now().replace(microsecond=0))
for track in self:
if not track.date:
track.is_track_live = track.is_track_soon = track.is_track_today = track.is_track_upcoming = track.is_track_done = False
track.track_start_relative = track.track_start_remaining = 0
continue
date_begin_utc = utc.localize(track.date, is_dst=False)
date_end_utc = utc.localize(track.date_end, is_dst=False)
track.is_track_live = date_begin_utc <= now_utc < date_end_utc
track.is_track_soon = (date_begin_utc - now_utc).total_seconds() < 30*60 if date_begin_utc > now_utc else False
track.is_track_today = date_begin_utc.date() == now_utc.date()
track.is_track_upcoming = date_begin_utc > now_utc
track.is_track_done = date_end_utc <= now_utc
if date_begin_utc >= now_utc:
track.track_start_relative = int((date_begin_utc - now_utc).total_seconds())
track.track_start_remaining = track.track_start_relative
else:
track.track_start_relative = int((now_utc - date_begin_utc).total_seconds())
track.track_start_remaining = 0
@api.depends('date', 'date_end', 'website_cta', 'website_cta_delay')
def _compute_cta_time_data(self):
""" Compute start and remaining time for track itself. Do everything in
UTC as we compute only time deltas here. """
now_utc = utc.localize(fields.Datetime.now().replace(microsecond=0))
for track in self:
if not track.website_cta:
track.is_website_cta_live = track.website_cta_start_remaining = False
continue
date_begin_utc = utc.localize(track.date, is_dst=False) + timedelta(minutes=track.website_cta_delay or 0)
date_end_utc = utc.localize(track.date_end, is_dst=False)
track.is_website_cta_live = date_begin_utc <= now_utc <= date_end_utc
if date_begin_utc >= now_utc:
td = date_begin_utc - now_utc
track.website_cta_start_remaining = int(td.total_seconds())
else:
track.website_cta_start_remaining = 0
# ------------------------------------------------------------
# CRUD
# ------------------------------------------------------------
@api.model_create_multi
def create(self, vals_list):
for values in vals_list:
if values.get('website_cta_url'):
values['website_cta_url'] = self.env['res.partner']._clean_website(values['website_cta_url'])
tracks = super(Track, self).create(vals_list)
for track in tracks:
@@ -239,6 +323,8 @@ class Track(models.Model):
return tracks
def write(self, vals):
if vals.get('website_cta_url'):
vals['website_cta_url'] = self.env['res.partner']._clean_website(vals['website_cta_url'])
if 'stage_id' in vals and 'kanban_state' not in vals:
vals['kanban_state'] = 'normal'
if vals.get('stage_id'):
@@ -317,6 +403,9 @@ class Track(models.Model):
'type': 'ir.actions.act_window',
}
def get_backend_menu_id(self):
return self.env.ref('event.event_main_menu').id
# ------------------------------------------------------------
# TOOLS
# ------------------------------------------------------------
@@ -352,3 +441,52 @@ class Track(models.Model):
} for track in missing])
return track_visitors
def _get_track_suggestions(self, restrict_domain=None, limit=None):
""" Returns the next tracks suggested after going to the current one
given by self. Tracks always belong to the same event.
Heuristic is
* live first;
* then ordered by start date, finished being sent to the end;
* wishlisted (manually or by default);
* tag matching with current track;
* location matching with current track;
* finally a random to have an "equivalent wave" randomly given;
:param restrict_domain: an additional domain to restrict candidates;
:param limit: number of tracks to return;
"""
self.ensure_one()
base_domain = [
'&',
('event_id', '=', self.event_id.id),
('id', '!=', self.id),
]
if restrict_domain:
base_domain = expression.AND([
base_domain,
restrict_domain
])
track_candidates = self.search(base_domain, limit=None, order='date asc')
if not track_candidates:
return track_candidates
track_candidates = track_candidates.sorted(
lambda track:
(track.is_published,
track.is_track_live,
track.track_start_remaining > 0,
-1 * track.track_start_remaining,
track.is_reminder_on,
not track.wishlisted_by_default,
len(track.tag_ids & self.tag_ids),
track.location_id == self.location_id,
randint(0, 20),
), reverse=True
)
return track_candidates[:limit]
@@ -2,4 +2,5 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import common
from . import test_track_internals
from . import test_website_event
@@ -5,6 +5,7 @@
<xpath expr="//link[last()]" position="after">
<link rel="stylesheet" href="/website_event_track/static/src/css/website_event_track.css"/>
<link rel="stylesheet" type="text/scss" href="/website_event_track/static/src/scss/event_track_templates.scss"/>
<link rel="stylesheet" type="text/scss" href="/website_event_track/static/src/scss/event_track_templates_online.scss"/>
<link rel="stylesheet" type="text/scss" href="/website_event_track/static/src/scss/pwa_frontend.scss"/>
</xpath>
<xpath expr="//script[last()]" position="after">
@@ -150,6 +150,9 @@
<template id="agenda_main_track" name="Track Agenda: Track">
<div class="d-flex flex-column h-100">
<div class="d-flex justify-content-end flex-wrap-reverse o_weagenda_track_badges">
<small t-if="track.is_track_live and not track.is_track_done and track.website_published"
class="mx-1 badge badge-danger">Live
</small>
<small t-if="not track.website_published and user_event_manager and track.stage_id.is_accepted"
title="Unpublished"
class="ml-1 badge badge-danger o_wevent_online_badge_unpublished">Unpublished</small>
@@ -1,93 +1,374 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="tracks" track="True">
<template id="tracks_session" name="Event Tracks">
<t t-call="website_event.layout">
<div class="oe_structure" id="oe_structure_website_event_track_tracks_1"/>
<section class="pt48 pb48">
<div class="container">
<div class="o_wevent_online o_wesession_index">
<!-- Options -->
<t t-set="option_track_wishlist" t-value="not event.is_done and request.website.viewref('website_event_track.session_topbar_wishlist').active"/>
<!-- Topbar -->
<t t-call="website_event_track.session_topbar"/>
<!-- Drag/Drop Area -->
<div id="oe_structure_wesession_index_1" class="oe_structure"/>
<!-- Content -->
<div class="o_wesession_container container">
<div class="row">
<div id="left_column">
</div>
<div class="col-lg-9">
<div class="alert alert-warning" t-if="not len(tracks)" role="alert">
No tracks found!
</div>
<div class="row mb-2" t-foreach="tracks" t-as="track">
<div class="col-md-2">
<t t-if="track.image">
<span t-field="track.image"
t-options='{"widget": "image", "class": "rounded-circle"}'/>
</t>
</div>
<div class="col-md-10">
<h3 class="mt0 mb0">
<a t-attf-href="/event/#{ slug(event) }/track/#{ slug(track) }" class="text-secondary"><span t-field="track.name"> </span></a>
<small t-if="not track.website_published" class="badge badge-danger">unpublished</small>
</h3>
<ul class="list-inline mb0">
<li t-if="track.partner_id" class="list-inline-item">
<i class="fa fa-user text-primary"/>
<t t-esc="track.partner_id.sudo().name"/>
</li>
<li t-if="track.date" class="list-inline-item">
<i class="fa fa-calendar text-primary"/>
<span t-field="track.date" t-options='{"hide_seconds":"True"}'/>
</li>
<li class="list-inline-item" t-if="track.location_id">
<i class="fa fa-map-marker"/>
<span t-field="track.location_id"/>
</li>
</ul>
<ul class="list-inline">
<li t-foreach="track.tag_ids" t-as="tag_id" class="list-inline-item fa fa-tags" t-if="tag_id.color != 0">
<a t-attf-href="/event/#{ slug(event) }/track/tag/#{ slug(tag_id) }">
<i class="fa fa-tags"/>
<span t-field="tag_id.name"/>
</a>
</li>
</ul>
<p class="mt8"><t t-esc="html2plaintext(track.description or '')[0:500]"/><t t-if="track.description">...</t></p>
</div>
</div>
</div>
<t t-call="website_event_track.tracks_search"/>
</div>
<div class="row">
<t t-call="website_event_track.tracks_main"/>
</div>
</div>
</section>
<div class="oe_structure" id="oe_structure_website_event_track_tracks_2"/>
<!-- Drag/Drop Area -->
<div id="oe_structure_wesession_index_2" class="oe_structure mb-5"/>
</div>
</t>
</template>
<template id="tracks_filter" inherit_id="website_event_track.tracks" customize_show="True" name="Filter on Tags">
<xpath expr="//div[@id='left_column']" position="inside">
<ul class="nav nav-pills flex-column">
<li class="nav-item"><a t-attf-href="/event/#{ slug(event) }/track" t-attf-class="nav-link #{'' if searches.get('tag') else ' active'}">All Tags</a></li>
<t t-foreach="tags" t-as="tag">
<li class="nav-item">
<a t-attf-href="/event/#{ slug(event) }/track/tag/#{ slug(tag) }" t-attf-class="nav-link#{searches.get('tag') == tag.id and ' active' or ''}">
<t t-esc="tag.name"/>
</a>
</li>
</t>
</ul>
</xpath>
<xpath expr="//div[@id='left_column']" position="attributes">
<attribute name="class">col-lg-3 css_no_print</attribute>
<!-- ============================================================ -->
<!-- TOPBAR: BASE NAVIGATION -->
<!-- ============================================================ -->
<!-- Main topbar -->
<template id="session_topbar" name="Tracks Tools">
<nav class="navbar navbar-light border-top shadow-sm d-print-none">
<div class="container">
<div class="d-flex flex-column flex-sm-row justify-content-between w-100">
<ul class="o_wesession_topbar_filters o_wevent_index_topbar_filters nav">
<!-- Optional wishlist filter -->
<li t-if="option_track_wishlist" class="nav-item dropdown mr-2 my-1">
<a href="#" role="button" class="btn dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-folder-open"/> Wishlist
</a>
<div class="dropdown-menu">
<a t-att-href="'/event/%s/track?%s' % (
slug(event),
keep_query('*', search_wishlist='')
)"
class="dropdown-item d-flex align-items-center justify-content-between">
All Talks
</a>
<a t-att-href="'/event/%s/track?%s' % (
slug(event),
keep_query('*', search_wishlist='1')
)"
t-attf-class="dropdown-item d-flex align-items-center justify-content-between #{'active' if search_wishlist else ''}">
Wishlisted
</a>
</div>
</li>
</ul>
<div class="d-flex align-items-center flex-wrap pl-sm-3 pr-0">
<t t-call="website_event.events_search_box">
<t t-set="_searches" t-value="searches"/>
<t t-set="action" t-value="'/event/%s/track' % (slug(event))"/>
<t t-set="_placeholder" t-value="'Search a talk ...'"/>
</t>
</div>
</div>
</div>
</nav>
</template>
<!-- Option: Topbar: optional tags filters -->
<template id="session_topbar_tag"
inherit_id="website_event_track.session_topbar"
name="Filter by Tags"
active="True"
customize_show="True">
<xpath expr="//ul[hasclass('o_wesession_topbar_filters')]" position="inside">
<t t-foreach="tag_categories" t-as="tag_category">
<li t-if="tag_category.tag_ids and any(tag.color for tag in tag_category.tag_ids)" class="nav-item dropdown mr-2 my-1">
<a href="#" role="button" class="btn dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-folder-open"/>
<t t-esc="tag_category.name"/>
</a>
<div class="dropdown-menu">
<t t-foreach="tag_category.tag_ids" t-as="tag">
<a t-att-href="'/event/%s/track?%s' % (
slug(event),
keep_query('*', tags=str((search_tags - tag).ids if tag in search_tags else (tag | search_tags).ids))
)"
t-if="tag.color"
t-attf-class="dropdown-item d-flex align-items-center justify-content-between #{'active' if tag in search_tags else ''}">
<t t-esc="tag.name"/>
</a>
</t>
</div>
</li>
</t>
</xpath>
</template>
<!-- Add wishlist button on tracks / track templates -->
<template id="tracks_wishlist"
name="Wishlist on Tracks"
inherit_id="website_event_track.tracks"
<!-- Option: Tracks display: optional wishlist -->
<template id="session_topbar_wishlist"
inherit_id="website_event_track.session_topbar"
name="Allow Wishlists"
active="True"
customize_show="True">
<xpath expr="//h3" position="replace">
<h3 class="mt0 mb0 d-flex">
<a t-attf-href="/event/#{ slug(event) }/track/#{ slug(track) }" class="text-secondary"><span t-field="track.name"> </span></a>
<small t-if="not track.website_published" class="badge badge-danger">unpublished</small>
<t t-call="website_event_track.track_widget_reminder"/>
</h3>
<xpath expr="//ul[hasclass('o_wesession_topbar_filters')]" position="inside">
</xpath>
</template>
<!-- ============================================================ -->
<!-- CONTENT: MAIN TEMPLATES -->
<!-- ============================================================ -->
<!-- Tracks Main Display -->
<template id="tracks_main" name="Tracks: Main Display">
<!-- No tracks -->
<t t-if="not tracks">
<div class="col-12">
<div class="h2 mb-3">No track found.</div>
<div t-if="search_key" class="alert alert-info text-center">
<p class="m-0">We did not find any track matching your <strong t-esc="search_key"/> search.</p>
</div>
<div t-else="" class="alert alert-info text-center" groups="event.group_event_manager">
<p class="m-0">Schedule some tracks to get started !</p>
</div>
</div>
</t>
<!-- Cards -->
<div class="col-12" t-call="website_event_track.tracks_display_cards"/>
<!-- List -->
<div class="col-12" t-call="website_event_track.tracks_display_list"/>
</template>
<!-- Tracks: Cards-based display -->
<template id="tracks_display_cards" name="Tracks: Cards Display">
<div class="row mb-3" t-if="tracks_live">
<div class="col-12">
<h5 class="m-0 text-danger">Live Now</h5>
<hr class="mt-2 pb-1 mb-1"/>
</div>
<div t-foreach="tracks_live" t-as="track" class="col-md-6 col-lg-3 mb-4">
<t t-call="website_event_track.tracks_cards_track"/>
</div>
</div>
<div class="row mb-3" t-if="tracks_soon">
<div class="col-12">
<h5 class="m-0 text-danger">Coming soon ...</h5>
<hr class="mt-2 pb-1 mb-1"/>
</div>
<div t-foreach="tracks_soon" t-as="track" class="col-md-6 col-lg-3 mb-4">
<t t-call="website_event_track.tracks_cards_track"/>
</div>
</div>
</template>
<!-- Tracks: List-based display -->
<template id="tracks_display_list" name="Tracks: List Display">
<div t-if="tracks" class="o_wesession_list mb-3">
<ul class="list-unstyled">
<li t-foreach="tracks_by_day" t-as="tracks_info"
class="mb-5">
<t t-set="tracks_date" t-value="tracks_info['date']"/>
<t t-set="tracks_header_name" t-value="tracks_info['name']"/>
<t t-set="tracks" t-value="tracks_info['tracks']"/>
<!-- DAY HEADER -->
<div class="o_we_track_day_header w-100">
<div t-if="tracks_date" class="d-flex">
<span class="h1 m-0 font-weight-bold" t-esc="tracks_date"
t-options="{'widget': 'date', 'format': 'EEEE dd'}"/>
<div class="d-flex flex-column ml-2">
<span class="font-weight-bold" t-esc="tracks_date"
t-options="{'widget': 'date', 'format': 'MMMM'}"/>
<span class="font-weight-bold" t-esc="tracks_date"
t-options="{'widget': 'date', 'format': 'YYYY'}"/>
</div>
</div>
<div t-elif="tracks_header_name">
<span class="h1 m-0 font-weight-bold"
t-esc="tracks_header_name"/>
</div>
<hr class="mt-2 pb-1 mb-1"/>
</div>
<!-- DAY TRACKS LIST -->
<div t-foreach="tracks" t-as="track" class="row mt8 o_wesession_list_item">
<div class="col-md-7">
<span class="h5 mb0">
<a t-if="track.is_published or user_event_manager"
t-att-href="track.website_url">
<span t-field="track.name"/>
</a>
<t t-else="">
<span t-field="track.name"/>
</t>
<span t-if="not track.is_published and user_event_manager"
class="badge badge-danger ml-2 o_wevent_online_badge_unpublished">
Unpublished
</span>
</span>
<div class="text-muted">
<span class="text-muted" t-esc="track.partner_name"/>
<t t-if="track.partner_name"> &amp;bull; </t>
<span t-esc="track.duration"
t-options="{'widget': 'duration', 'digital': False, 'format': 'short', 'unit': 'hour', 'round': 'minute'}"/>
</div>
</div>
<div class="col-md-5">
<div t-if="not event.is_done" class="float-right">
<t t-if="tracks_date and today_tz &lt;= tracks_date">
<span t-if="track.is_track_live and not track.is_track_done"
class="badge badge-danger">Live</span>
<span t-elif="not track.is_track_done and track.is_track_soon">
<span t-esc="track.track_start_remaining"
t-options="{'widget': 'duration', 'digital': False, 'format': 'narrow',
'add_direction': True, 'unit': 'second', 'round': 'minute'}"/>
</span>
<t t-elif="not track.is_track_done and not track.is_track_soon">
<span
class="mr-2"
t-esc="track.date"
t-options="{'widget': 'datetime', 'time_only': True, 'format': 'short'}"/>
<t t-if="option_track_wishlist"
t-call="website_event_track.track_widget_reminder">
<t t-set="reminder_small" t-value="True"/>
</t>
</t>
<t t-else="">
<span class="badge badge-info">Finished</span>
</t>
</t>
<t t-elif="not track.date and option_track_wishlist"
t-call="website_event_track_online.track_widget_reminder">
<t t-set="reminder_small" t-value="True"/>
</t>
</div>
<t t-foreach="track.tag_ids" t-as="tag">
<t t-if="tag.color" t-call="website_event_track.track_tag_badge_link"/>
</t>
</div>
</div>
</li>
</ul>
</div>
</template>
<!-- ============================================================ -->
<!-- TOOL TEMPLATES -->
<!-- ============================================================ -->
<template id="tracks_cards_track" name="Track Card">
<a t-att-href="'/event/%s/track/%s' % (slug(track.event_id), slug(track))" class="text-decoration-none">
<article t-att-class="'h-100 card border-0 shadow-sm o_wesession_track_card %s' % ('o_wesession_track_card_unpublished' if not track.is_published else '')"
itemscope="itemscope" itemtype="http://schema.org/Event">
<div class="h-100 row no-gutters">
<header class="overflow-hidden bg-secondary col-12 rounded-top">
<small t-if="not track.is_published" class="o_wesession_track_card_header_badge bg-danger">
<i class="fa fa-ban mr-2"/>Unpublished
</small>
<div t-if="track.website_image_url" class="card-img-top"
t-attf-style="padding-top: 50%; background-image: url(#{track.website_image_url}); background-size: cover; background-position:center">
<span t-if="option_track_wishlist and not track.is_track_live" class="position-absolute h3 mt-2 mr-2" style="right: 0; top: 0;">
<t t-call="website_event_track.track_widget_reminder">
<t t-set="reminder_light" t-value="True"/>
<t t-set="light_theme" t-value="True"/>
</t>
</span>
</div>
<div t-else="" class="o_wesession_gradient card-img-top position-relative"
style="padding-top: 50%;">
<span t-if="option_track_wishlist" class="position-absolute h3 mt-2 mr-2" style="right: 0; top: 0;">
<t t-call="website_event_track.track_widget_reminder">
<t t-set="reminder_light" t-value="True"/>
</t>
</span>
<i class="fa fa-glass fa-2x mx-2 mb-3 position-absolute text-white-75" style="right:0; bottom: 0;"/>
</div>
</header>
<div class="col-12">
<main class="card-body">
<!-- Title -->
<h5 class="card-title mt-0 mb-0 text-truncate">
<span t-field="track.name" itemprop="name"/>
</h5>
<!-- Tags> -->
<div>
<t t-foreach="track.tag_ids" t-as="tag">
<t t-if="tag.color" t-call="website_event_track.track_tag_badge_info"/>
</t>
</div>
</main>
</div>
<!-- Footer -->
<footer class="small align-self-end w-100 card-footer">
<div class="d-flex justify-content-between align-items-center">
<!-- Speaker -->
<span class="text-muted text-truncate" t-field="track.partner_name" itemprop="performer"/>
<!-- Starts -->
<span class="text-muted ml-auto">
<t t-if="track.is_track_upcoming &gt; 0">In
<span class="text-muted ml-auto" t-field="track.track_start_remaining" itemprop="duration"
t-options="{'widget': 'duration', 'digital': False, 'format': 'short', 'unit': 'second', 'round': 'minute'}"/>
</t>
<t t-else="">
<span class="text-muted ml-auto" t-field="track.track_start_relative" itemprop="duration"
t-options="{'widget': 'duration', 'digital': False, 'format': 'short', 'unit': 'second', 'round': 'minute'}"/>
ago
</t>
</span>
</div>
</footer>
</div>
</article>
</a>
</template>
<!-- Searched tags -->
<template id="tracks_search" name="Tracks: search tags">
<div class="d-flex align-items-center mb-3">
<span t-if="search_wishlist"
class="align-items-baseline border d-inline-flex pl-2 mt-3 rounded ml16 mb-2 bg-white">
<i class="fa fa-bell mr-2 text-muted"/> Wishlisted Talks
<a t-att-href="'/event/%s/track?%s' % (slug(event), keep_query('*', search_wishlist=''))"
class="btn border-0 py-1">
&#215;
</a>
</span>
<t t-foreach="search_tags" t-as="tag">
<span class="align-items-baseline border d-inline-flex pl-2 mt-3 rounded ml16 mb-2 bg-white">
<i class="fa fa-tag mr-2 text-muted"/>
<t t-esc="tag.display_name"/>
<a t-att-href="'/event/%s/track?%s' % (slug(event), keep_query('*', tags=str((search_tags - tag).ids)))"
class="btn border-0 py-1">
&#215;
</a>
</span>
</t>
</div>
</template>
<!-- ============================================================ -->
<!-- MISC TOOLS -->
<!-- ============================================================ -->
<template id="track_tag_badge_link" name="Track: Tag Badge Link">
<a t-if="search_tags"
t-att-href="'/event/%s/track?%s'% (
slug(event),
keep_query('*', tags=str((search_tags - tag).ids if tag in search_tags else (tag | search_tags).ids))
)"
t-att-class="'badge %s' % ('badge-primary' if tag in search_tags else 'o_tag_color_hovered_0')"
t-esc="tag.name"/>
<a t-else=""
t-att-href="'/event/%s/track?%s'% (
slug(event),
keep_query('*', tags=str(tag.ids))
)"
t-att-class="'badge o_tag_color_hovered_%s' % (tag.color)"
t-esc="tag.name"/>
</template>
<template id="track_tag_badge_info" name="Track: Tag Badge Info">
<span t-if="search_tags"
t-att-class="'badge %s' % ('badge-primary' if tag in search_tags else 'o_tag_color_0')"
t-esc="tag.name"/>
<span t-else=""
t-att-class="'badge o_tag_color_%s' % (tag.color)"
t-esc="tag.name"/>
</template>
</odoo>
@@ -1,99 +1,216 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="track_view" track="True">
<template id="event_track_main" name="Event Exhibitor">
<t t-call="website_event.layout">
<section class="pt48">
<div class="container">
<div class="row">
<div class="col-12">
<h1 t-field="track.name"/>
<small>Proposed by
<span t-if="track.partner_id" t-field="track.partner_id.sudo().name"/>
<span t-if="not track.partner_id" t-field="track.partner_name"/>
</small>
<ul t-if="track.tag_ids" class="text-center text-muted list-inline">
<li t-foreach="track.tag_ids" t-as="tag_id" class="list-inline-item" t-if="tag_id.color != 0">
<span class="fa fa-tags"></span>
<a t-attf-href="/event/#{ slug(event) }/track/tag/#{ slug(tag_id) }">
<span t-field="tag_id.name"/>
</a>
</li>
</ul>
</div>
<div class="o_wevent_online o_wevent_online_bg o_wesession_index">
<!-- Options -->
<t t-set="option_widescreen" t-value="option_widescreen or False"/>
<t t-set="option_track_wishlist" t-value="not event.is_done and request.website.viewref('website_event_track.session_topbar_wishlist').active"/>
<!-- Drag/Drop Area -->
<div id="oe_structure_wesession_track_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_track.event_track_aside"/>
<t t-call="website_event_track.event_track_content"/>
</div>
</div>
</section>
<section class="pt32">
<div class="container">
<div class="row">
<div class="col-lg-8">
<div t-field="track.description"/>
<h3>About The Speaker</h3>
<div t-field="track.partner_biography"/>
<div t-if="track.partner_id" class="row mt32">
<div class="col-md-2">
<span t-if="track.image" t-field="track.image" t-options="{'widget': 'image', 'class': 'rounded-circle'}"/>
<span t-else="" t-field="track.partner_id.sudo().image_128" t-options='{"widget": "image", "class": "rounded-circle"}'/>
</div>
<div class="col-md-10">
<h4 t-field="track.partner_id.name" class="mb4"/>
<div class="mb16" 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>
</div>
<div t-field="track.partner_id.website_description"/>
</div>
</div>
</div>
<div class="col-lg-4" id="right_column">
<div class="card">
<h4 class="card-header">Practical Info</h4>
<div class="card-body">
<b>Date</b><br/>
<t t-if="track.date">
<span t-field="track.date" t-options='{"hide_seconds":"True", "format": "short"}'/>
<t t-if="event.date_tz">
(<span t-field="track.date" t-options='{"format": "zzz"}'/>)
</t>
</t>
<t t-else="">
<span>To be scheduled</span>
</t>
<br/>
<b>Duration</b><br/>
<span t-field="track.duration" t-options='{"widget": "duration", "unit": "hour", "round": "minute"}'/><br/>
<b>Location</b><br/>
<span t-field="track.location_id"/><br/>
</div>
</div>
<div class="card" t-if="False">
<h4 class="card-header">Documents</h4>
<div class="card-body">
Put here the list of documents, like slides of
the presentations. Remove the above t-if when
it's implemented.
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Drag/Drop Area -->
<div id="oe_structure_wesession_track_index_2" class="oe_structure"/>
</div>
</t>
</template>
<template id="track_view_wishlist"
name="Wishlist on Track"
inherit_id="website_event_track.track_view"
customize_show="True">
<xpath expr="//h1" position="replace">
<div class="d-flex">
<h1 t-field="track.name"/>
<div class="ml-2 pt-1">
<t t-call="website_event_track.track_widget_reminder"/>
<!-- ============================================================ -->
<!-- CONTENT: MAIN TEMPLATES -->
<!-- ============================================================ -->
<template id="event_track_content" name="Track: Main Description">
<div name="o_wesession_track_main"
t-att-class="'col-12 o_wevent_online_page_main o_wesession_track_main bg-white p-0 %s' % ('col-md-9 col-lg-10' if option_widescreen else 'col-md-8 col-lg-9')">
<!-- LIVE INFORMATIONS -->
<div t-if="not track.event_id.is_ongoing" class="pt-3">
<div class="mx-3 alert alert-warning">
Event <span t-esc="track.event_id.name" class="font-weight-bold"/>
<span t-if="track.event_id.start_today">
starts in
<span t-esc="track.event_id.start_remaining"
t-options="{'widget': 'duration', 'digital': False, 'unit': 'minute', 'round': 'minute'}"/>
</span>
<span t-else="">
starts at
<span t-field="track.event_id.with_context(tz=track.event_id.date_tz).date_begin"
t-options="{'format': 'long', 'tz_name': track.event_id.date_tz, 'hide_seconds': True}"/>
</span>
</div>
</div>
</xpath>
<div t-elif="track.track_start_remaining" class="pt-3">
<div class="mx-3 alert alert-warning">
Starting in
<span t-esc="track.track_start_remaining"
t-options="{'widget': 'duration', 'digital': False, 'unit': 'second', 'round': 'minute'}"/>
</div>
</div>
<!-- TRACK DESCRIPTION -->
<div class="o_wesession_track_main_description">
<div class="mx-3 pt-3 mb-3 d-flex justify-content-between flex-column flex-md-row">
<div class="d-flex flex-column">
<span class="h4 mb-0" t-esc="track.name"/>
<div>
<t t-foreach="track.tag_ids" t-as="tag">
<span t-if="tag.color"
t-att-class="'badge o_tag_color_hovered_%s' % (tag.color)"
t-esc="tag.name"/>
</t>
</div>
</div>
<div class="o_we_track_action_buttons d-flex justify-content-md-end align-items-center flex-wrap">
<div class="o_we_track_reminder_button my-1">
<div t-if="option_track_wishlist and not track.is_track_done"
t-call="website_event_track.track_widget_reminder">
<t t-set="reminder_small" t-value="False"/>
<t t-set="reminder_light" t-value="False"/>
</div>
</div>
</div>
</div>
<div class="mx-3 text-muted">
<t t-if="track.location_id">
<strong>Location:</strong> <span t-field="track.location_id"/>
<span> - </span>
</t>
<t t-if="track.date">
<span t-field="track.date"
t-options='{"hide_seconds":"True", "format": "short"}'/>
-
<span t-field="track.date_end"
t-options='{"hide_seconds":"True", "format": "short"}'/>
<t t-if="event.date_tz">
(<span t-field="track.date_end" t-options='{"format": "zzz"}'/>)
</t>
</t>
<t t-if="track.duration">
(<span t-field="track.duration"
t-options='{"widget": "duration", "unit": "hour", "round": "minute"}'/>)
</t>
</div>
<hr class="mt-2 mb-0"/>
<!-- ABOUT AUTHOR -->
<div class="mx-3">
<div class="mt-2 d-flex">
<span t-if="track.image" t-field="track.image" class="o_wevent_online_page_avatar"
t-options="{'widget': 'image', 'class': 'rounded-circle', 'max_width': '96'}"/>
<div class="pl-2 pr-0 pr-md-2 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>&amp;nbsp;at&amp;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>
<span class="mb-1 d-flex align-items-baseline text-break" t-if="track.partner_email">
<i class="fa fa-envelope mr-2"/><a t-att-mailto="track.partner_email"><span t-field="track.partner_email"/></a>
</span>
<span class="mb-1 d-flex align-items-baseline text-break" t-if="track.partner_phone">
<i class="fa fa-phone mr-2"/><span t-field="track.partner_phone"/>
</span>
</div>
</div>
<div t-field="track.partner_biography" class="oe_no_empty"/>
<hr t-if="not is_html_empty(track.description)" class="mt-2 pb-1 mb-1"/>
</div>
<div t-field="track.description" class="my-2 mx-3 oe_no_empty"/>
</div>
</div>
</template>
<!-- ============================================================ -->
<!-- ASIDE: CONTROL PANEL -->
<!-- ============================================================ -->
<template id="event_track_aside" name="Track: Aside">
<div t-att-class="'col-12 pl-0 pr-0 pr-md-3 o_wevent_online_page_aside o_wesession_track_aside %s' % ('col-md-3 col-lg-2' if option_widescreen else 'col-md-4 col-lg-3')">
<div class="bg-white pt-0 pt-md-3 o_wevent_online_page_aside_content">
<div class="mx-2" t-if="track.website_cta">
<t t-set="cta_coundown" t-value="bool(track.website_cta_start_remaining)"/>
<div t-if="cta_coundown" class="text-center mt-2 mt-md-0 w-100 btn btn-primary d-none">
<t t-call="website_event.display_timer_widget">
<t t-set="pre_remaining_time" t-value="int(track.track_start_remaining)"/>
<!--<t t-set="pre_countdown_text" t-value="'Talk starts in'"/>-->
<t t-set="pre_countdown_display" t-value="bool(False)"/>
<t t-set="main_remaining_time" t-value="int(track.website_cta_start_remaining)"/>
<!--<t t-set="main_countdown_text" t-value="'Magic happens in'"/>-->
<t t-set="main_countdown_display" t-value="bool(False)"/>
<t t-set="display_class" t-value="'.o_event_cta_action'"/>
</t>
</div>
<div t-att-class="'o_event_cta_action %s' % ('d-none' if cta_coundown else '')">
<a t-att-href="track.website_cta_url"
target="_blank"
class="btn btn-primary w-100 mb-3 mt-2 mt-md-0">
<span t-esc="track.website_cta_title"/>
</a>
</div>
</div>
<div class="d-flex align-items-center justify-content-between mx-2">
<ul class="nav nav-tabs o_wesession_track_aside_nav d-flex border-0" role="tablist">
<li class="nav-item flex-grow-1">
<a href="#track_list" aria-controls="track_list" class="nav-link active" role="tab" data-toggle="tab">
Talks
</a>
</li>
</ul>
<a href="#collapse_track_aside" data-toggle="collapse" class="d-md-none p-2 text-decoration-none o_wevent_online_page_aside_collapse collapsed">
<i class="fa fa-chevron-down d-md-none"/>
</a>
</div>
<div id="collapse_track_aside"
class="tab-content collapse d-md-block o_wesession_track_aside_tabs">
<div class="tab-pane fade show active" id="track_list" role="tabpanel">
<ul class="list-unstyled mb-0">
<li t-foreach="tracks_other" t-as="track_other" class="w-100">
<a t-if="user_event_manager or track_other.is_published"
class="d-block w-100 h-100 px-2 pt-2 pb-1 text-decoration-none"
t-att-href="track_other.website_url">
<t t-call="website_event_track.event_track_aside_other_track"/>
</a>
<div t-else="" class="text-muted px-2 pt-2 pb-1">
<t t-call="website_event_track.event_track_aside_other_track"/>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
<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_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>
<small t-elif="track_other.is_track_done"
class="badge badge-light">Done</small>
<small t-elif="track_other.is_track_today and track_other.track_start_remaining"
class="badge badge-light">
<span t-esc="track_other.track_start_remaining"
t-options="{'widget': 'duration', 'digital': False, 'add_direction': True,
'unit': 'second', 'round': 'minute', 'format': 'narrow'}"/>
</small>
<div t-elif="track_other.date" class="badge badge-light">
<span t-esc="track_other.date" t-options="{'widget': 'datetime', 'tz_name': track_other.event_id.date_tz, 'format': 'MMM. dd'}"/>
</div>
</div>
</div>
</template>
</odoo>
@@ -183,6 +183,25 @@
<page string="Description" name="description">
<field name="description"/>
</page>
<page string="Interactivity" name="interactivity">
<group>
<group name="event_track_cta_group">
<field name="website_cta"/>
<field name="website_cta_title" placeholder="e.g. Get Yours Now !"
attrs="{'invisible': [('website_cta', '=', False)],
'required': [('website_cta', '=', True)]}"/>
<field name="website_cta_url" placeholder="e.g. http://www.example.com"
attrs="{'invisible': [('website_cta', '=', False)],
'required': [('website_cta', '=', True)]}"/>
<label for="website_cta_delay"
attrs="{'invisible': [('website_cta', '=', False)]}"/>
<div attrs="{'invisible': [('website_cta', '=', False)]}">
<field name="website_cta_delay" class="oe_inline"
attrs="{'required': [('website_cta', '=', True)]}"/> minutes after talk start
</div>
</group>
</group>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
@@ -12,7 +12,6 @@
'description': "",
'depends': [
'website_event_track',
'website_event_track_session',
],
'data': [
'views/assets.xml',
@@ -3,11 +3,11 @@
import re
from odoo.addons.website_event_track_session.controllers.session import WebsiteEventSessionController
from odoo.addons.website_event_track.controllers.event_track import EventTrackController
from odoo.http import request
class WebsiteEventSessionLiveController(WebsiteEventSessionController):
class WebsiteEventSessionLiveController(EventTrackController):
def _event_track_page_get_values(self, event, track, **options):
if 'widescreen' not in options:
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="tracks_display_list" inherit_id="website_event_track_session.tracks_display_list">
<template id="tracks_display_list" inherit_id="website_event_track.tracks_display_list">
<!-- TRACK LIST: ADD REPLAY TAG FOR FINISHED TRACKS -->
<xpath expr="//div[hasclass('o_wesession_list_item')]/div[hasclass('col-md-5')]//t[@t-elif='not track.is_track_done and not track.is_track_soon']" position="after">
<a t-elif="track.youtube_video_url and (track.is_published or user_event_manager)"
@@ -2,7 +2,7 @@
<odoo>
<!-- MAIN: REMOVE HEADER/FOOTER IF WIDESCREEN -->
<template id="event_track_main" inherit_id="website_event_track_session.event_track_main">
<template id="event_track_main" inherit_id="website_event_track.event_track_main">
<xpath expr="//t[@t-call='website_event.layout']" position="before">
<t t-set="no_header" t-value="option_widescreen"/>
<t t-set="no_footer" t-value="option_widescreen"/>
@@ -10,7 +10,7 @@
</template>
<!-- MAIN: ADD VIDEO -->
<template id="event_track_content" inherit_id="website_event_track_session.event_track_content">
<template id="event_track_content" inherit_id="website_event_track.event_track_content">
<!-- Do not display "starting in" in case of replay mode as video is already available -->
<xpath expr="//div[@t-elif='track.track_start_remaining']" position="attributes">
<attribute name="t-elif">track.track_start_remaining and not track.is_youtube_replay</attribute>
@@ -31,7 +31,7 @@
</template>
<!-- ASIDE: ADD CHAT TAB FOR VIDEOS -->
<template id="event_track_aside" inherit_id="website_event_track_session.event_track_aside">
<template id="event_track_aside" inherit_id="website_event_track.event_track_aside">
<xpath expr="//ul[hasclass('o_wesession_track_aside_nav')]" position="inside">
<li t-if="track.is_youtube_chat_available and not is_mobile_chat_disabled" class="nav-item flex-grow-1">
<a href="#track_chat" aria-controls="track_chat" class="nav-link" role="tab" data-toggle="tab">
@@ -12,7 +12,7 @@
'description': "",
'depends': [
'website_profile',
'website_event_track_session',
'website_event_track',
],
'data': [
'security/ir.model.access.csv',
@@ -2,11 +2,11 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import http
from odoo.addons.website_event_track_session.controllers.session import WebsiteEventSessionController
from odoo.addons.website_event_track.controllers.event_track import EventTrackController
from odoo.http import request
class WebsiteEventTrackQuiz(WebsiteEventSessionController):
class WebsiteEventTrackQuiz(EventTrackController):
# QUIZZES IN PAGE
# ----------------------------------------------------------
@@ -3,7 +3,7 @@
<template id="event_track_content"
name="Track: Main Description: add quiz"
inherit_id="website_event_track_session.event_track_content">
inherit_id="website_event_track.event_track_content">
<xpath expr="//div[hasclass('o_wesession_track_main_description')]" position="after">
<div id="we_track_quiz_container" t-if="track.quiz_id"
t-att-class="'o_quiz_js_quiz_container o_quiz_main border-top bg-white px-2 pt-4 pb-4 col-12 %s' % ('' if track.is_quiz_completed else 'd-none')"
@@ -3,7 +3,7 @@
<record id="event_track_view_form" model="ir.ui.view" >
<field name="name">event.track.view.form.inherit.event.track.quiz</field>
<field name="model">event.track</field>
<field name="inherit_id" ref="website_event_track_session.event_track_view_form"/>
<field name="inherit_id" ref="website_event_track.view_event_track_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='event_track_cta_group']" position="after">
<group name="event_track_quiz_group">
@@ -1,6 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import controllers
from . import models
from . import tests
@@ -1,29 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Event Live Sessions',
'category': 'Marketing/Events',
'sequence': 1005,
'version': '1.0',
'summary': 'Event: live session on tracks',
'website': 'https://www.odoo.com/page/events',
'description': "",
'depends': [
'website_event_track',
],
'data': [
'views/assets.xml',
'views/event_track_templates_agenda.xml',
'views/event_track_templates_list.xml',
'views/event_track_templates_misc.xml',
'views/event_track_templates_page.xml',
'views/event_track_views.xml',
],
'demo': [
'data/event_track_demo.xml',
],
'application': False,
'installable': True,
}
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import session
@@ -1,207 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from ast import literal_eval
from pytz import timezone, utc
from werkzeug.exceptions import NotFound
from odoo import fields, http, _
from odoo.addons.website_event_track.controllers.event_track import EventTrackController
from odoo.http import request
from odoo.osv import expression
from odoo.tools import is_html_empty
class WebsiteEventSessionController(EventTrackController):
def _get_event_tracks_base_domain(self, event):
""" Base domain for displaying tracks. Restrict to accepted or published
tracks for people not managing events. Unpublished tracks may be displayed
but not reachable for teasing purpose. """
search_domain_base = [
('event_id', '=', event.id),
]
if not request.env.user.has_group('event.group_event_user'):
search_domain_base = expression.AND([
search_domain_base,
['|', ('is_published', '=', True), ('is_accepted', '=', True)]
])
return search_domain_base
# ------------------------------------------------------------
# MAIN PAGE
# ------------------------------------------------------------
@http.route([
'''/event/<model("event.event", "[('website_track', '=', True)]"):event>/track''',
'''/event/<model("event.event", "[('website_track', '=', True)]"):event>/track/tag/<model("event.track.tag"):tag>'''
], type='http', auth="public", website=True, sitemap=False)
def event_tracks(self, event, tag=None, **searches):
""" Main route
:param event: event whose tracks are about to be displayed;
:param tag: deprecated: search for a specific tag
:param searches: frontend search dict, containing
* 'search': search string;
* 'tags': list of tag IDs for filtering;
"""
if not event.can_access_from_current_website():
raise NotFound()
return request.render(
"website_event_track.tracks",
self._event_tracks_get_values(event, tag=tag, **searches)
)
def _event_tracks_get_values(self, event, tag=None, **searches):
# init and process search terms
searches.setdefault('search', '')
searches.setdefault('search_wishlist', '')
searches.setdefault('tags', '')
search_domain = self._get_event_tracks_base_domain(event)
# search on content
if searches.get('search'):
search_domain = expression.AND([
search_domain,
[('name', 'ilike', searches['search'])]
])
# search on tags
search_tags = self._get_search_tags(searches['tags'])
if not search_tags and tag: # backward compatibility
search_tags = tag
if search_tags:
# Example: You filter on age: 10-12 and activity: football.
# Doing it this way allows to only get events who are tagged "age: 10-12" AND "activity: football".
# Add another tag "age: 12-15" to the search and it would fetch the ones who are tagged:
# ("age: 10-12" OR "age: 12-15") AND "activity: football
grouped_tags = dict()
for search_tag in search_tags:
grouped_tags.setdefault(search_tag.category_id, list()).append(search_tag)
search_domain_items = [
[('tag_ids', 'in', [tag.id for tag in grouped_tags[group]])]
for group in grouped_tags
]
search_domain = expression.AND([
search_domain,
*search_domain_items
])
# fetch data to display with TZ set for both event and tracks
now_tz = utc.localize(fields.Datetime.now().replace(microsecond=0), is_dst=False).astimezone(timezone(event.date_tz))
today_tz = now_tz.date()
event = event.with_context(tz=event.date_tz or 'UTC')
tracks_sudo = event.env['event.track'].sudo().search(search_domain, order='date asc')
tag_categories = request.env['event.track.tag.category'].sudo().search([])
# filter on wishlist (as post processing due to costly search on is_reminder_on)
if searches.get('search_wishlist'):
tracks_sudo = tracks_sudo.filtered(lambda track: track.is_reminder_on)
# organize categories for display: announced, live, soon and day-based
tracks_announced = tracks_sudo.filtered(lambda track: not track.date)
tracks_wdate = tracks_sudo - tracks_announced
date_begin_tz_all = list(set(
dt.date()
for dt in self._get_dt_in_event_tz(tracks_wdate.mapped('date'), event)
))
date_begin_tz_all.sort()
tracks_sudo_live = tracks_wdate.filtered(lambda track: track.is_published and track.is_track_live)
tracks_sudo_soon = tracks_wdate.filtered(lambda track: track.is_published and not track.is_track_live and track.is_track_soon)
tracks_by_day = []
for display_date in date_begin_tz_all:
matching_tracks = tracks_wdate.filtered(lambda track: self._get_dt_in_event_tz([track.date], event)[0].date() == display_date)
tracks_by_day.append({'date': display_date, 'name': display_date, 'tracks': matching_tracks})
if tracks_announced:
tracks_announced = tracks_announced.sorted('wishlisted_by_default', reverse=True)
tracks_by_day.append({'date': False, 'name': _('Coming soon'), 'tracks': tracks_announced})
# return rendering values
return {
# event information
'event': event,
'main_object': event,
# tracks display information
'tracks': tracks_sudo,
'tracks_by_day': tracks_by_day,
'tracks_live': tracks_sudo_live,
'tracks_soon': tracks_sudo_soon,
'today_tz': today_tz,
# search information
'searches': searches,
'search_key': searches['search'],
'search_wishlist': searches['search_wishlist'],
'search_tags': search_tags,
'tag_categories': tag_categories,
# environment
'is_html_empty': is_html_empty,
'hostname': request.httprequest.host.split(':')[0],
'user_event_manager': request.env.user.has_group('event.group_event_manager'),
}
# ------------------------------------------------------------
# PAGE VIEW
# ------------------------------------------------------------
@http.route('''/event/<model("event.event", "[('website_track', '=', True)]"):event>/track/<model("event.track", "[('event_id', '=', event.id)]"):track>''',
type='http', auth="public", website=True, sitemap=True)
def event_track_page(self, event, track, **options):
track = self._fetch_track(track.id, allow_is_accepted=False)
return request.render(
"website_event_track_session.event_track_main",
self._event_track_page_get_values(event, track.sudo(), **options)
)
def event_track_view(self, event, track, **post):
return self.event_track_page(event, track, **post)
def _event_track_page_get_values(self, event, track, **options):
track = track.sudo()
option_widescreen = options.get('widescreen', False)
option_widescreen = bool(option_widescreen) if option_widescreen != '0' else False
# search for tracks list
tracks_other = track._get_track_suggestions(
restrict_domain=self._get_event_tracks_base_domain(track.event_id),
limit=10
)
return {
# event information
'event': event,
'main_object': track,
'track': track,
# sidebar
'tracks_other': tracks_other,
# options
'option_widescreen': option_widescreen,
# environment
'is_html_empty': is_html_empty,
'hostname': request.httprequest.host.split(':')[0],
'user_event_manager': request.env.user.has_group('event.group_event_manager'),
}
# ------------------------------------------------------------
# TOOLS
# ------------------------------------------------------------
def _get_search_tags(self, tag_search):
# TDE FIXME: make me generic (slides, event, ...)
try:
tag_ids = literal_eval(tag_search)
except Exception:
tags = request.env['event.track.tag'].sudo()
else:
# perform a search to filter on existing / valid tags implicitly
tags = request.env['event.track.tag'].sudo().search([('id', 'in', tag_ids)])
return tags
def _get_dt_in_event_tz(self, datetimes, event):
tz_name = event.date_tz
return [
utc.localize(dt, is_dst=False).astimezone(timezone(tz_name))
for dt in datetimes
]
@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo><data>
<record id="website_event_track.event_7_track_1" model="event.track">
<field name="website_cta" eval="True"/>
<field name="website_cta_title">Try Now</field>
<field name="website_cta_url">http://www.example.com</field>
<field name="website_cta_delay">10</field>
</record>
<record id="website_event_track.event_7_track_4" model="event.track">
<field name="website_cta" eval="True"/>
<field name="website_cta_title">Try Now</field>
<field name="website_cta_url">http://www.example.com</field>
<field name="website_cta_delay">10</field>
</record>
</data></odoo>
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import event_track
@@ -1,155 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import timedelta
from random import randint
from pytz import utc
from odoo import api, fields, models
from odoo.osv import expression
class EventTrack(models.Model):
_inherit = "event.track"
# Call to action
website_cta = fields.Boolean('Magic Button')
website_cta_title = fields.Char('Button Title')
website_cta_url = fields.Char('Button Target URL')
website_cta_delay = fields.Integer('Button appears')
# time information for track
is_track_live = fields.Boolean(
'Is Track Live', compute='_compute_track_time_data',
help="Track has started and is ongoing")
is_track_soon = fields.Boolean(
'Is Track Soon', compute='_compute_track_time_data',
help="Track begins soon")
is_track_today = fields.Boolean(
'Is Track Today', compute='_compute_track_time_data',
help="Track begins today")
is_track_upcoming = fields.Boolean(
'Is Track Upcoming', compute='_compute_track_time_data',
help="Track is not yet started")
is_track_done = fields.Boolean(
'Is Track Done', compute='_compute_track_time_data',
help="Track is finished")
track_start_remaining = fields.Integer(
'Minutes before track starts', compute='_compute_track_time_data',
help="Remaining time before track starts (seconds)")
track_start_relative = fields.Integer(
'Minutes compare to track start', compute='_compute_track_time_data',
help="Relative time compared to track start (seconds)")
# time information for CTA
is_website_cta_live = fields.Boolean(
'Is CTA Live', compute='_compute_cta_time_data',
help="CTA button is available")
website_cta_start_remaining = fields.Integer(
'Minutes before CTA starts', compute='_compute_cta_time_data',
help="Remaining time before CTA starts (seconds)")
@api.depends('date', 'date_end')
def _compute_track_time_data(self):
""" Compute start and remaining time for track itself. Do everything in
UTC as we compute only time deltas here. """
now_utc = utc.localize(fields.Datetime.now().replace(microsecond=0))
for track in self:
if not track.date:
track.is_track_live = track.is_track_soon = track.is_track_today = track.is_track_upcoming = track.is_track_done = False
track.track_start_relative = track.track_start_remaining = 0
continue
date_begin_utc = utc.localize(track.date, is_dst=False)
date_end_utc = utc.localize(track.date_end, is_dst=False)
track.is_track_live = date_begin_utc <= now_utc < date_end_utc
track.is_track_soon = (date_begin_utc - now_utc).total_seconds() < 30*60 if date_begin_utc > now_utc else False
track.is_track_today = date_begin_utc.date() == now_utc.date()
track.is_track_upcoming = date_begin_utc > now_utc
track.is_track_done = date_end_utc <= now_utc
if date_begin_utc >= now_utc:
track.track_start_relative = int((date_begin_utc - now_utc).total_seconds())
track.track_start_remaining = track.track_start_relative
else:
track.track_start_relative = int((now_utc - date_begin_utc).total_seconds())
track.track_start_remaining = 0
@api.depends('date', 'date_end', 'website_cta', 'website_cta_delay')
def _compute_cta_time_data(self):
""" Compute start and remaining time for track itself. Do everything in
UTC as we compute only time deltas here. """
now_utc = utc.localize(fields.Datetime.now().replace(microsecond=0))
for track in self:
if not track.website_cta:
track.is_website_cta_live = track.website_cta_start_remaining = False
continue
date_begin_utc = utc.localize(track.date, is_dst=False) + timedelta(minutes=track.website_cta_delay or 0)
date_end_utc = utc.localize(track.date_end, is_dst=False)
track.is_website_cta_live = date_begin_utc <= now_utc <= date_end_utc
if date_begin_utc >= now_utc:
td = date_begin_utc - now_utc
track.website_cta_start_remaining = int(td.total_seconds())
else:
track.website_cta_start_remaining = 0
@api.model_create_multi
def create(self, values_list):
for values in values_list:
if values.get('website_cta_url'):
values['website_cta_url'] = self.env['res.partner']._clean_website(values['website_cta_url'])
return super(EventTrack, self).create(values_list)
def write(self, values):
if values.get('website_cta_url'):
values['website_cta_url'] = self.env['res.partner']._clean_website(values['website_cta_url'])
return super(EventTrack, self).write(values)
def _get_track_suggestions(self, restrict_domain=None, limit=None):
""" Returns the next tracks suggested after going to the current one
given by self. Tracks always belong to the same event.
Heuristic is
* live first;
* then ordered by start date, finished being sent to the end;
* wishlisted (manually or by default);
* tag matching with current track;
* location matching with current track;
* finally a random to have an "equivalent wave" randomly given;
:param restrict_domain: an additional domain to restrict candidates;
:param limit: number of tracks to return;
"""
self.ensure_one()
base_domain = [
'&',
('event_id', '=', self.event_id.id),
('id', '!=', self.id),
]
if restrict_domain:
base_domain = expression.AND([
base_domain,
restrict_domain
])
track_candidates = self.search(base_domain, limit=None, order='date asc')
if not track_candidates:
return track_candidates
track_candidates = track_candidates.sorted(
lambda track:
(track.is_published,
track.is_track_live,
track.track_start_remaining > 0,
-1 * track.track_start_remaining,
track.is_reminder_on,
not track.wishlisted_by_default,
len(track.tag_ids & self.tag_ids),
track.location_id == self.location_id,
randint(0, 20),
), reverse=True
)
return track_candidates[:limit]
def get_backend_menu_id(self):
return self.env.ref('event.event_main_menu').id
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_track_internals
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="assets_frontend" inherit_id="website.assets_frontend" name="Event Sessions Assets Frontend">
<xpath expr="//link[last()]" position="after">
<link rel="stylesheet" type="text/scss" href="/website_event_track_session/static/src/scss/event_track_templates.scss"/>
</xpath>
</template>
</odoo>
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="agenda_main_track" name="Track Agenda: Track (Session)"
inherit_id="website_event_track.agenda_main_track">
<xpath expr="//div[hasclass('o_weagenda_track_badges')]/small[1]" position="before">
<small t-if="track.is_track_live and not track.is_track_done and track.website_published"
class="mx-1 badge badge-danger">Live
</small>
</xpath>
</template>
</odoo>
@@ -1,380 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="tracks_session"
inherit_id="website_event_track.tracks">
<!-- New main layout -->
<xpath expr="//div[@id='oe_structure_website_event_track_tracks_1']" position="before">
<div class="o_wevent_online o_wesession_index">
<!-- Options -->
<t t-set="option_track_wishlist" t-value="not event.is_done and request.website.viewref('website_event_track_session.session_topbar_wishlist').active"/>
<!-- Topbar -->
<t t-call="website_event_track_session.session_topbar"/>
<!-- Drag/Drop Area -->
<div id="oe_structure_wesession_index_1" class="oe_structure"/>
<!-- Content -->
<div class="o_wesession_container container">
<div class="row">
<t t-call="website_event_track_session.tracks_search"/>
</div>
<div class="row">
<t t-call="website_event_track_session.tracks_main"/>
</div>
</div>
<!-- Drag/Drop Area -->
<div id="oe_structure_wesession_index_2" class="oe_structure mb-5"/>
</div>
</xpath>
<!-- Reset existing template as nothing to keep -->
<xpath expr="//div[@id='oe_structure_website_event_track_tracks_1']" position="replace"/>
<xpath expr="//section" position="replace"/>
<xpath expr="//div[@id='oe_structure_website_event_track_tracks_2']" position="replace"/>
</template>
<!-- ============================================================ -->
<!-- TOPBAR: BASE NAVIGATION -->
<!-- ============================================================ -->
<!-- Main topbar -->
<template id="session_topbar" name="Tracks Tools">
<nav class="navbar navbar-light border-top shadow-sm d-print-none">
<div class="container">
<div class="d-flex flex-column flex-sm-row justify-content-between w-100">
<ul class="o_wesession_topbar_filters o_wevent_index_topbar_filters nav">
<!-- Optional wishlist filter -->
<li t-if="option_track_wishlist" class="nav-item dropdown mr-2 my-1">
<a href="#" role="button" class="btn dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-folder-open"/> Wishlist
</a>
<div class="dropdown-menu">
<a t-att-href="'/event/%s/track?%s' % (
slug(event),
keep_query('*', search_wishlist='')
)"
class="dropdown-item d-flex align-items-center justify-content-between">
All Talks
</a>
<a t-att-href="'/event/%s/track?%s' % (
slug(event),
keep_query('*', search_wishlist='1')
)"
t-attf-class="dropdown-item d-flex align-items-center justify-content-between #{'active' if search_wishlist else ''}">
Wishlisted
</a>
</div>
</li>
</ul>
<div class="d-flex align-items-center flex-wrap pl-sm-3 pr-0">
<t t-call="website_event.events_search_box">
<t t-set="_searches" t-value="searches"/>
<t t-set="action" t-value="'/event/%s/track' % (slug(event))"/>
<t t-set="_placeholder" t-value="'Search a talk ...'"/>
</t>
</div>
</div>
</div>
</nav>
</template>
<!-- Option: Topbar: optional tags filters -->
<template id="session_topbar_tag"
inherit_id="website_event_track_session.session_topbar"
name="Filter by Tags"
active="True"
customize_show="True">
<xpath expr="//ul[hasclass('o_wesession_topbar_filters')]" position="inside">
<t t-foreach="tag_categories" t-as="tag_category">
<li t-if="tag_category.tag_ids and any(tag.color for tag in tag_category.tag_ids)" class="nav-item dropdown mr-2 my-1">
<a href="#" role="button" class="btn dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-folder-open"/>
<t t-esc="tag_category.name"/>
</a>
<div class="dropdown-menu">
<t t-foreach="tag_category.tag_ids" t-as="tag">
<a t-att-href="'/event/%s/track?%s' % (
slug(event),
keep_query('*', tags=str((search_tags - tag).ids if tag in search_tags else (tag | search_tags).ids))
)"
t-if="tag.color"
t-attf-class="dropdown-item d-flex align-items-center justify-content-between #{'active' if tag in search_tags else ''}">
<t t-esc="tag.name"/>
</a>
</t>
</div>
</li>
</t>
</xpath>
</template>
<!-- Option: Tracks display: optional wishlist -->
<template id="session_topbar_wishlist"
inherit_id="website_event_track_session.session_topbar"
name="Allow Wishlists"
active="True"
customize_show="True">
<xpath expr="//ul[hasclass('o_wesession_topbar_filters')]" position="inside">
</xpath>
</template>
<!-- ============================================================ -->
<!-- CONTENT: MAIN TEMPLATES -->
<!-- ============================================================ -->
<!-- Tracks Main Display -->
<template id="tracks_main" name="Tracks: Main Display">
<!-- No tracks -->
<t t-if="not tracks">
<div class="col-12">
<div class="h2 mb-3">No track found.</div>
<div t-if="search_key" class="alert alert-info text-center">
<p class="m-0">We did not find any track matching your <strong t-esc="search_key"/> search.</p>
</div>
<div t-else="" class="alert alert-info text-center" groups="event.group_event_manager">
<p class="m-0">Schedule some tracks to get started !</p>
</div>
</div>
</t>
<!-- Cards -->
<div class="col-12" t-call="website_event_track_session.tracks_display_cards"/>
<!-- List -->
<div class="col-12" t-call="website_event_track_session.tracks_display_list"/>
</template>
<!-- Tracks: Cards-based display -->
<template id="tracks_display_cards" name="Tracks: Cards Display">
<div class="row mb-3" t-if="tracks_live">
<div class="col-12">
<h5 class="m-0 text-danger">Live Now</h5>
<hr class="mt-2 pb-1 mb-1"/>
</div>
<div t-foreach="tracks_live" t-as="track" class="col-md-6 col-lg-3 mb-4">
<t t-call="website_event_track_session.tracks_cards_track"/>
</div>
</div>
<div class="row mb-3" t-if="tracks_soon">
<div class="col-12">
<h5 class="m-0 text-danger">Coming soon ...</h5>
<hr class="mt-2 pb-1 mb-1"/>
</div>
<div t-foreach="tracks_soon" t-as="track" class="col-md-6 col-lg-3 mb-4">
<t t-call="website_event_track_session.tracks_cards_track"/>
</div>
</div>
</template>
<!-- Tracks: List-based display -->
<template id="tracks_display_list" name="Tracks: List Display">
<div t-if="tracks" class="o_wesession_list mb-3">
<ul class="list-unstyled">
<li t-foreach="tracks_by_day" t-as="tracks_info"
class="mb-5">
<t t-set="tracks_date" t-value="tracks_info['date']"/>
<t t-set="tracks_header_name" t-value="tracks_info['name']"/>
<t t-set="tracks" t-value="tracks_info['tracks']"/>
<!-- DAY HEADER -->
<div class="o_we_track_day_header w-100">
<div t-if="tracks_date" class="d-flex">
<span class="h1 m-0 font-weight-bold" t-esc="tracks_date"
t-options="{'widget': 'date', 'format': 'EEEE dd'}"/>
<div class="d-flex flex-column ml-2">
<span class="font-weight-bold" t-esc="tracks_date"
t-options="{'widget': 'date', 'format': 'MMMM'}"/>
<span class="font-weight-bold" t-esc="tracks_date"
t-options="{'widget': 'date', 'format': 'YYYY'}"/>
</div>
</div>
<div t-elif="tracks_header_name">
<span class="h1 m-0 font-weight-bold"
t-esc="tracks_header_name"/>
</div>
<hr class="mt-2 pb-1 mb-1"/>
</div>
<!-- DAY TRACKS LIST -->
<div t-foreach="tracks" t-as="track" class="row mt8 o_wesession_list_item">
<div class="col-md-7">
<span class="h5 mb0">
<a t-if="track.is_published or user_event_manager"
t-att-href="track.website_url">
<span t-field="track.name"/>
</a>
<t t-else="">
<span t-field="track.name"/>
</t>
<span t-if="not track.is_published and user_event_manager"
class="badge badge-danger ml-2 o_wevent_online_badge_unpublished">
Unpublished
</span>
</span>
<div class="text-muted">
<span class="text-muted" t-esc="track.partner_name"/>
<t t-if="track.partner_name"> &amp;bull; </t>
<span t-esc="track.duration"
t-options="{'widget': 'duration', 'digital': False, 'format': 'short', 'unit': 'hour', 'round': 'minute'}"/>
</div>
</div>
<div class="col-md-5">
<div t-if="not event.is_done" class="float-right">
<t t-if="tracks_date and today_tz &lt;= tracks_date">
<span t-if="track.is_track_live and not track.is_track_done"
class="badge badge-danger">Live</span>
<span t-elif="not track.is_track_done and track.is_track_soon">
<span t-esc="track.track_start_remaining"
t-options="{'widget': 'duration', 'digital': False, 'format': 'narrow',
'add_direction': True, 'unit': 'second', 'round': 'minute'}"/>
</span>
<t t-elif="not track.is_track_done and not track.is_track_soon">
<span
class="mr-2"
t-esc="track.date"
t-options="{'widget': 'datetime', 'time_only': True, 'format': 'short'}"/>
<t t-if="option_track_wishlist"
t-call="website_event_track.track_widget_reminder">
<t t-set="reminder_small" t-value="True"/>
</t>
</t>
<t t-else="">
<span class="badge badge-info">Finished</span>
</t>
</t>
<t t-elif="not track.date and option_track_wishlist"
t-call="website_event_track_online.track_widget_reminder">
<t t-set="reminder_small" t-value="True"/>
</t>
</div>
<t t-foreach="track.tag_ids" t-as="tag">
<t t-if="tag.color" t-call="website_event_track_session.track_tag_badge_link"/>
</t>
</div>
</div>
</li>
</ul>
</div>
</template>
<!-- ============================================================ -->
<!-- TOOL TEMPLATES -->
<!-- ============================================================ -->
<template id="tracks_cards_track" name="Track Card">
<a t-att-href="'/event/%s/track/%s' % (slug(track.event_id), slug(track))" class="text-decoration-none">
<article t-att-class="'h-100 card border-0 shadow-sm o_wesession_track_card %s' % ('o_wesession_track_card_unpublished' if not track.is_published else '')"
itemscope="itemscope" itemtype="http://schema.org/Event">
<div class="h-100 row no-gutters">
<header class="overflow-hidden bg-secondary col-12 rounded-top">
<small t-if="not track.is_published" class="o_wesession_track_card_header_badge bg-danger">
<i class="fa fa-ban mr-2"/>Unpublished
</small>
<div t-if="track.website_image_url" class="card-img-top"
t-attf-style="padding-top: 50%; background-image: url(#{track.website_image_url}); background-size: cover; background-position:center">
<span t-if="option_track_wishlist and not track.is_track_live" class="position-absolute h3 mt-2 mr-2" style="right: 0; top: 0;">
<t t-call="website_event_track.track_widget_reminder">
<t t-set="reminder_light" t-value="True"/>
<t t-set="light_theme" t-value="True"/>
</t>
</span>
</div>
<div t-else="" class="o_wesession_gradient card-img-top position-relative"
style="padding-top: 50%;">
<span t-if="option_track_wishlist" class="position-absolute h3 mt-2 mr-2" style="right: 0; top: 0;">
<t t-call="website_event_track.track_widget_reminder">
<t t-set="reminder_light" t-value="True"/>
</t>
</span>
<i class="fa fa-glass fa-2x mx-2 mb-3 position-absolute text-white-75" style="right:0; bottom: 0;"/>
</div>
</header>
<div class="col-12">
<main class="card-body">
<!-- Title -->
<h5 class="card-title mt-0 mb-0 text-truncate">
<span t-field="track.name" itemprop="name"/>
</h5>
<!-- Tags> -->
<div>
<t t-foreach="track.tag_ids" t-as="tag">
<t t-if="tag.color" t-call="website_event_track_session.track_tag_badge_info"/>
</t>
</div>
</main>
</div>
<!-- Footer -->
<footer class="small align-self-end w-100 card-footer">
<div class="d-flex justify-content-between align-items-center">
<!-- Speaker -->
<span class="text-muted text-truncate" t-field="track.partner_name" itemprop="performer"/>
<!-- Starts -->
<span class="text-muted ml-auto">
<t t-if="track.is_track_upcoming &gt; 0">In
<span class="text-muted ml-auto" t-field="track.track_start_remaining" itemprop="duration"
t-options="{'widget': 'duration', 'digital': False, 'format': 'short', 'unit': 'second', 'round': 'minute'}"/>
</t>
<t t-else="">
<span class="text-muted ml-auto" t-field="track.track_start_relative" itemprop="duration"
t-options="{'widget': 'duration', 'digital': False, 'format': 'short', 'unit': 'second', 'round': 'minute'}"/>
ago
</t>
</span>
</div>
</footer>
</div>
</article>
</a>
</template>
<!-- Searched tags -->
<template id="tracks_search" name="Tracks: search tags">
<div class="d-flex align-items-center mb-3">
<span t-if="search_wishlist"
class="align-items-baseline border d-inline-flex pl-2 mt-3 rounded ml16 mb-2 bg-white">
<i class="fa fa-bell mr-2 text-muted"/> Wishlisted Talks
<a t-att-href="'/event/%s/track?%s' % (slug(event), keep_query('*', search_wishlist=''))"
class="btn border-0 py-1">
&#215;
</a>
</span>
<t t-foreach="search_tags" t-as="tag">
<span class="align-items-baseline border d-inline-flex pl-2 mt-3 rounded ml16 mb-2 bg-white">
<i class="fa fa-tag mr-2 text-muted"/>
<t t-esc="tag.display_name"/>
<a t-att-href="'/event/%s/track?%s' % (slug(event), keep_query('*', tags=str((search_tags - tag).ids)))"
class="btn border-0 py-1">
&#215;
</a>
</span>
</t>
</div>
</template>
<!-- ============================================================ -->
<!-- MISC TOOLS -->
<!-- ============================================================ -->
<template id="track_tag_badge_link" name="Track: Tag Badge Link">
<a t-if="search_tags"
t-att-href="'/event/%s/track?%s'% (
slug(event),
keep_query('*', tags=str((search_tags - tag).ids if tag in search_tags else (tag | search_tags).ids))
)"
t-att-class="'badge %s' % ('badge-primary' if tag in search_tags else 'o_tag_color_hovered_0')"
t-esc="tag.name"/>
<a t-else=""
t-att-href="'/event/%s/track?%s'% (
slug(event),
keep_query('*', tags=str(tag.ids))
)"
t-att-class="'badge o_tag_color_hovered_%s' % (tag.color)"
t-esc="tag.name"/>
</template>
<template id="track_tag_badge_info" name="Track: Tag Badge Info">
<span t-if="search_tags"
t-att-class="'badge %s' % ('badge-primary' if tag in search_tags else 'o_tag_color_0')"
t-esc="tag.name"/>
<span t-else=""
t-att-class="'badge o_tag_color_%s' % (tag.color)"
t-esc="tag.name"/>
</template>
</odoo>
@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo><data>
<!-- De-activate old customize -->
<record id="website_event_track.tracks_filter" model="ir.ui.view">
<field name="active" eval="False"/>
<field name="customize_show" eval="False"/>
</record>
<record id="website_event_track.tracks_wishlist" model="ir.ui.view">
<field name="active" eval="False"/>
<field name="customize_show" eval="False"/>
</record>
<record id="website_event_track.track_view_wishlist" model="ir.ui.view">
<field name="active" eval="False"/>
<field name="customize_show" eval="False"/>
</record>
</data></odoo>
@@ -1,216 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="event_track_main" name="Event Exhibitor">
<t t-call="website_event.layout">
<div class="o_wevent_online o_wevent_online_bg o_wesession_index">
<!-- Options -->
<t t-set="option_widescreen" t-value="option_widescreen or False"/>
<t t-set="option_track_wishlist" t-value="not event.is_done and request.website.viewref('website_event_track_session.session_topbar_wishlist').active"/>
<!-- Drag/Drop Area -->
<div id="oe_structure_wesession_track_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_track_session.event_track_aside"/>
<t t-call="website_event_track_session.event_track_content"/>
</div>
</div>
<!-- Drag/Drop Area -->
<div id="oe_structure_wesession_track_index_2" class="oe_structure"/>
</div>
</t>
</template>
<!-- ============================================================ -->
<!-- CONTENT: MAIN TEMPLATES -->
<!-- ============================================================ -->
<template id="event_track_content" name="Track: Main Description">
<div name="o_wesession_track_main"
t-att-class="'col-12 o_wevent_online_page_main o_wesession_track_main bg-white p-0 %s' % ('col-md-9 col-lg-10' if option_widescreen else 'col-md-8 col-lg-9')">
<!-- LIVE INFORMATIONS -->
<div t-if="not track.event_id.is_ongoing" class="pt-3">
<div class="mx-3 alert alert-warning">
Event <span t-esc="track.event_id.name" class="font-weight-bold"/>
<span t-if="track.event_id.start_today">
starts in
<span t-esc="track.event_id.start_remaining"
t-options="{'widget': 'duration', 'digital': False, 'unit': 'minute', 'round': 'minute'}"/>
</span>
<span t-else="">
starts at
<span t-field="track.event_id.with_context(tz=track.event_id.date_tz).date_begin"
t-options="{'format': 'long', 'tz_name': track.event_id.date_tz, 'hide_seconds': True}"/>
</span>
</div>
</div>
<div t-elif="track.track_start_remaining" class="pt-3">
<div class="mx-3 alert alert-warning">
Starting in
<span t-esc="track.track_start_remaining"
t-options="{'widget': 'duration', 'digital': False, 'unit': 'second', 'round': 'minute'}"/>
</div>
</div>
<!-- TRACK DESCRIPTION -->
<div class="o_wesession_track_main_description">
<div class="mx-3 pt-3 mb-3 d-flex justify-content-between flex-column flex-md-row">
<div class="d-flex flex-column">
<span class="h4 mb-0" t-esc="track.name"/>
<div>
<t t-foreach="track.tag_ids" t-as="tag">
<span t-if="tag.color"
t-att-class="'badge o_tag_color_hovered_%s' % (tag.color)"
t-esc="tag.name"/>
</t>
</div>
</div>
<div class="o_we_track_action_buttons d-flex justify-content-md-end align-items-center flex-wrap">
<div class="o_we_track_reminder_button my-1">
<div t-if="option_track_wishlist and not track.is_track_done"
t-call="website_event_track.track_widget_reminder">
<t t-set="reminder_small" t-value="False"/>
<t t-set="reminder_light" t-value="False"/>
</div>
</div>
</div>
</div>
<div class="mx-3 text-muted">
<t t-if="track.location_id">
<strong>Location:</strong> <span t-field="track.location_id"/>
<span> - </span>
</t>
<t t-if="track.date">
<span t-field="track.date"
t-options='{"hide_seconds":"True", "format": "short"}'/>
-
<span t-field="track.date_end"
t-options='{"hide_seconds":"True", "format": "short"}'/>
<t t-if="event.date_tz">
(<span t-field="track.date_end" t-options='{"format": "zzz"}'/>)
</t>
</t>
<t t-if="track.duration">
(<span t-field="track.duration"
t-options='{"widget": "duration", "unit": "hour", "round": "minute"}'/>)
</t>
</div>
<hr class="mt-2 mb-0"/>
<!-- ABOUT AUTHOR -->
<div class="mx-3">
<div class="mt-2 d-flex">
<span t-if="track.image" t-field="track.image" class="o_wevent_online_page_avatar"
t-options="{'widget': 'image', 'class': 'rounded-circle', 'max_width': '96'}"/>
<div class="pl-2 pr-0 pr-md-2 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>&amp;nbsp;at&amp;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>
<span class="mb-1 d-flex align-items-baseline text-break" t-if="track.partner_email">
<i class="fa fa-envelope mr-2"/><a t-att-mailto="track.partner_email"><span t-field="track.partner_email"/></a>
</span>
<span class="mb-1 d-flex align-items-baseline text-break" t-if="track.partner_phone">
<i class="fa fa-phone mr-2"/><span t-field="track.partner_phone"/>
</span>
</div>
</div>
<div t-field="track.partner_biography" class="oe_no_empty"/>
<hr t-if="not is_html_empty(track.description)" class="mt-2 pb-1 mb-1"/>
</div>
<div t-field="track.description" class="my-2 mx-3 oe_no_empty"/>
</div>
</div>
</template>
<!-- ============================================================ -->
<!-- ASIDE: CONTROL PANEL -->
<!-- ============================================================ -->
<template id="event_track_aside" name="Track: Aside">
<div t-att-class="'col-12 pl-0 pr-0 pr-md-3 o_wevent_online_page_aside o_wesession_track_aside %s' % ('col-md-3 col-lg-2' if option_widescreen else 'col-md-4 col-lg-3')">
<div class="bg-white pt-0 pt-md-3 o_wevent_online_page_aside_content">
<div class="mx-2" t-if="track.website_cta">
<t t-set="cta_coundown" t-value="bool(track.website_cta_start_remaining)"/>
<div t-if="cta_coundown" class="text-center mt-2 mt-md-0 w-100 btn btn-primary d-none">
<t t-call="website_event.display_timer_widget">
<t t-set="pre_remaining_time" t-value="int(track.track_start_remaining)"/>
<!--<t t-set="pre_countdown_text" t-value="'Talk starts in'"/>-->
<t t-set="pre_countdown_display" t-value="bool(False)"/>
<t t-set="main_remaining_time" t-value="int(track.website_cta_start_remaining)"/>
<!--<t t-set="main_countdown_text" t-value="'Magic happens in'"/>-->
<t t-set="main_countdown_display" t-value="bool(False)"/>
<t t-set="display_class" t-value="'.o_event_cta_action'"/>
</t>
</div>
<div t-att-class="'o_event_cta_action %s' % ('d-none' if cta_coundown else '')">
<a t-att-href="track.website_cta_url"
target="_blank"
class="btn btn-primary w-100 mb-3 mt-2 mt-md-0">
<span t-esc="track.website_cta_title"/>
</a>
</div>
</div>
<div class="d-flex align-items-center justify-content-between mx-2">
<ul class="nav nav-tabs o_wesession_track_aside_nav d-flex border-0" role="tablist">
<li class="nav-item flex-grow-1">
<a href="#track_list" aria-controls="track_list" class="nav-link active" role="tab" data-toggle="tab">
Talks
</a>
</li>
</ul>
<a href="#collapse_track_aside" data-toggle="collapse" class="d-md-none p-2 text-decoration-none o_wevent_online_page_aside_collapse collapsed">
<i class="fa fa-chevron-down d-md-none"/>
</a>
</div>
<div id="collapse_track_aside"
class="tab-content collapse d-md-block o_wesession_track_aside_tabs">
<div class="tab-pane fade show active" id="track_list" role="tabpanel">
<ul class="list-unstyled mb-0">
<li t-foreach="tracks_other" t-as="track_other" class="w-100">
<a t-if="user_event_manager or track_other.is_published"
class="d-block w-100 h-100 px-2 pt-2 pb-1 text-decoration-none"
t-att-href="track_other.website_url">
<t t-call="website_event_track_session.event_track_aside_other_track"/>
</a>
<div t-else="" class="text-muted px-2 pt-2 pb-1">
<t t-call="website_event_track_session.event_track_aside_other_track"/>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
<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_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>
<small t-elif="track_other.is_track_done"
class="badge badge-light">Done</small>
<small t-elif="track_other.is_track_today and track_other.track_start_remaining"
class="badge badge-light">
<span t-esc="track_other.track_start_remaining"
t-options="{'widget': 'duration', 'digital': False, 'add_direction': True,
'unit': 'second', 'round': 'minute', 'format': 'narrow'}"/>
</small>
<div t-elif="track_other.date" class="badge badge-light">
<span t-esc="track_other.date" t-options="{'widget': 'datetime', 'tz_name': track_other.event_id.date_tz, 'format': 'MMM. dd'}"/>
</div>
</div>
</div>
</template>
</odoo>
@@ -1,33 +0,0 @@
<?xml version="1.0"?>
<odoo>
<record id="event_track_view_form" model="ir.ui.view">
<field name="name">event.track.view.form.inherit.session</field>
<field name="model">event.track</field>
<field name="inherit_id" ref="website_event_track.view_event_track_form"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='description']" position="after">
<page string="Interactivity" name="interactivity">
<group>
<group name="event_track_cta_group">
<field name="website_cta"/>
<field name="website_cta_title" placeholder="e.g. Get Yours Now !"
attrs="{'invisible': [('website_cta', '=', False)],
'required': [('website_cta', '=', True)]}"/>
<field name="website_cta_url" placeholder="e.g. http://www.example.com"
attrs="{'invisible': [('website_cta', '=', False)],
'required': [('website_cta', '=', True)]}"/>
<label for="website_cta_delay"
attrs="{'invisible': [('website_cta', '=', False)]}"/>
<div attrs="{'invisible': [('website_cta', '=', False)]}">
<field name="website_cta_delay" class="oe_inline"
attrs="{'required': [('website_cta', '=', True)]}"/> minutes after talk start
</div>
</group>
</group>
</page>
</xpath>
</field>
</record>
</odoo>