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 Purpose of this commit is to completely redesign the talks pages and frontend flow. Notably better showcase live or upcoming talks, have a usable tracks list, provide reworked page view with tracks suggestions, ... SPECIFICATIONS Mockups are given in https://docs.google.com/presentation/d/1VSxp2luUvp9xLFq9d1Wy-6IC0mP0NA8vLmh44WE5PCs/edit#slide=id.g891af57392_0_84 Main ideas are * new pages: view for all tracks and view for a specific track * list view: display two sections (Live Now, Coming Soon) displaying track cards. After that a list view of tracks by date. This page allows to filter tracks on tags and search on them; * page view: revamped view of a track. Two tabs: main content holds track description and information. Aside tab contains suggestions of tracks based on a given heuristics; SPECIFICATIONS: LIST VIEW: LIVE SECTIONS Have 2 sections: Live Now and Coming Soon. Live Now contains tracks currently running. Coming Soon displays tracks that begins within 30 minutes. In those sections, tracks are listed as cards with basic informations: cover picture, title, speakers, remaining time before start or time since it has started, ... Use the reminder widget to allow people to wishlist tracks. Clicking on a card redirect to the track page view. SPECIFICATIONS: LIST VIEW Display a list view of tracks under those sections. Use a table-like display in desktop, more card-based in mobile to ease reading. Each day is a section of the list view, a bit like the agenda. For each track, display its name, tags, speaker, duration. If it starts today, display remaining time. Otherwise display its start datetime. For done tracks, do not display any time information as it is not useful anymore. Display reminder widget on tracks that are not done. Accepted stages should be displayed text-only to customers. That way they know the track exists but they cannot see the track page. Even managers see links to be able to edit or publish tracks. SPECIFICATIONS: PAGE VIEW Display a main content tab, with track description and speaker information. Display an aside tab, with track suggestions following this heuristics * 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; As with track list, accepted tracks are not clickable for customers. In mobile, main content should take the whole screen and aside should be collapsed. That way we put emphasis on main content. SPECIFICATIONS: CTA BUTTON Allow to configure "Call to action" button on a track. It is now possible to give configuration of a button that appears on the track page view, above the other track suggestions tab. This CTA should be configured using: its name, its URL, and its delay compared to the track beginning. Before this delay it is not displayed. JS widget ensure button is displayed even if the page is not reloaded, to make a super wow effect especially with live session on track. KNOWN LIMITATIONS Tracks do not hold any protection for payment-based tickets. Everything that is published is available using standard ACLs. Content protection based on tickets is currently not supported. LINKS Task ID-2252655 (Main Online Event task) Task ID-2283735 (Talks -> Session upgrade) Co-Authored-By: Aurélien Warnon <awa@odoo.com> Co-Authored-By: David Beguin <dbe@odoo.com> Co-Authored-By: Thibault Delavallée <tde@odoo.com>
201 lines
8.3 KiB
Python
201 lines
8.3 KiB
Python
# -*- 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 Forbidden, NotFound
|
|
|
|
from odoo import exceptions, fields, http
|
|
from odoo.addons.website_event_track_online.controllers.event_track import EventTrackOnlineController
|
|
from odoo.http import request
|
|
from odoo.osv import expression
|
|
from odoo.tools import is_html_empty
|
|
|
|
|
|
class WebsiteEventSessionController(EventTrackOnlineController):
|
|
|
|
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),
|
|
('date', '!=', False)
|
|
]
|
|
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"):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, **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: live, soon and day-based
|
|
date_begin_tz_all = list(set(
|
|
dt.date()
|
|
for dt in self._get_dt_in_event_tz(tracks_sudo.mapped('date'), event)
|
|
))
|
|
date_begin_tz_all.sort()
|
|
tracks_sudo_live = tracks_sudo.filtered(lambda track: track.is_published and track.is_track_live)
|
|
tracks_sudo_soon = tracks_sudo.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_track = tracks_sudo.filtered(lambda track: self._get_dt_in_event_tz([track.date], event)[0].date() == display_date)
|
|
tracks_by_day.append((display_date, matching_track))
|
|
|
|
# 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"):event>/track/<model("event.track"):track>',
|
|
type='http', auth="public", website=True, sitemap=False)
|
|
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_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
|
|
]
|