Files
odoo_source/addons/website_event_track_session/controllers/session.py
T
e73ec2ddb9 [ADD] website_event_track_session: new session mode for tracks
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>
2020-08-04 14:28:29 +00:00

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
]