From de8a5652b27da2f659b1378831c6d712ff40b488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 20 Aug 2020 14:03:52 +0000 Subject: [PATCH] [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@981f95bbf46658eda9f2d33751b9310cc67d86aa and odoo/enterprise@14722028ae0773c3710ea48ce48ff44703e83b5a 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 --- addons/test_event_full/__manifest__.py | 1 - .../controllers/event_track.py | 191 +++++++- .../data/event_track_demo.xml | 8 + .../website_event_track/models/event_track.py | 138 ++++++ .../scss/event_track_templates_online.scss} | 0 addons/website_event_track/tests/__init__.py | 1 + .../tests/test_track_internals.py | 0 addons/website_event_track/views/assets.xml | 1 + .../views/event_track_templates_agenda.xml | 3 + .../views/event_track_templates_list.xml | 431 +++++++++++++++--- .../views/event_track_templates_page.xml | 287 ++++++++---- .../views/event_track_views.xml | 19 + .../website_event_track_live/__manifest__.py | 1 - .../controllers/session.py | 4 +- .../views/event_track_templates_list.xml | 2 +- .../views/event_track_templates_page.xml | 6 +- .../website_event_track_quiz/__manifest__.py | 2 +- .../controllers/event_track_quiz.py | 4 +- .../views/event_track_templates_page.xml | 2 +- .../views/event_track_views.xml | 2 +- .../website_event_track_session/__init__.py | 6 - .../__manifest__.py | 29 -- .../controllers/__init__.py | 4 - .../controllers/session.py | 207 --------- .../data/event_track_demo.xml | 17 - .../models/__init__.py | 4 - .../models/event_track.py | 155 ------- .../tests/__init__.py | 4 - .../views/assets.xml | 10 - .../views/event_track_templates_agenda.xml | 13 - .../views/event_track_templates_list.xml | 380 --------------- .../views/event_track_templates_misc.xml | 18 - .../views/event_track_templates_page.xml | 216 --------- .../views/event_track_views.xml | 33 -- 34 files changed, 907 insertions(+), 1292 deletions(-) rename addons/{website_event_track_session/static/src/scss/event_track_templates.scss => website_event_track/static/src/scss/event_track_templates_online.scss} (100%) rename addons/{website_event_track_session => website_event_track}/tests/test_track_internals.py (100%) delete mode 100644 addons/website_event_track_session/__init__.py delete mode 100644 addons/website_event_track_session/__manifest__.py delete mode 100644 addons/website_event_track_session/controllers/__init__.py delete mode 100644 addons/website_event_track_session/controllers/session.py delete mode 100644 addons/website_event_track_session/data/event_track_demo.xml delete mode 100644 addons/website_event_track_session/models/__init__.py delete mode 100644 addons/website_event_track_session/models/event_track.py delete mode 100644 addons/website_event_track_session/tests/__init__.py delete mode 100644 addons/website_event_track_session/views/assets.xml delete mode 100644 addons/website_event_track_session/views/event_track_templates_agenda.xml delete mode 100644 addons/website_event_track_session/views/event_track_templates_list.xml delete mode 100644 addons/website_event_track_session/views/event_track_templates_misc.xml delete mode 100644 addons/website_event_track_session/views/event_track_templates_page.xml delete mode 100644 addons/website_event_track_session/views/event_track_views.xml diff --git a/addons/test_event_full/__manifest__.py b/addons/test_event_full/__manifest__.py index bd0263e341c..8423fc06d7f 100644 --- a/addons/test_event_full/__manifest__.py +++ b/addons/test_event_full/__manifest__.py @@ -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', ], diff --git a/addons/website_event_track/controllers/event_track.py b/addons/website_event_track/controllers/event_track.py index e338f384580..782b1e839ee 100644 --- a/addons/website_event_track/controllers/event_track.py +++ b/addons/website_event_track/controllers/event_track.py @@ -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//track''', '''/event//track/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//track/2 + + Try Now + http://www.example.com + 10 First Day Wrapup @@ -387,6 +391,10 @@ 2 + + Try Now + http://www.example.com + 10 diff --git a/addons/website_event_track/models/event_track.py b/addons/website_event_track/models/event_track.py index 2ec57bddde4..9c44d2d290d 100644 --- a/addons/website_event_track/models/event_track.py +++ b/addons/website_event_track/models/event_track.py @@ -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] diff --git a/addons/website_event_track_session/static/src/scss/event_track_templates.scss b/addons/website_event_track/static/src/scss/event_track_templates_online.scss similarity index 100% rename from addons/website_event_track_session/static/src/scss/event_track_templates.scss rename to addons/website_event_track/static/src/scss/event_track_templates_online.scss diff --git a/addons/website_event_track/tests/__init__.py b/addons/website_event_track/tests/__init__.py index 12b9da864c8..aac974d7656 100644 --- a/addons/website_event_track/tests/__init__.py +++ b/addons/website_event_track/tests/__init__.py @@ -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 diff --git a/addons/website_event_track_session/tests/test_track_internals.py b/addons/website_event_track/tests/test_track_internals.py similarity index 100% rename from addons/website_event_track_session/tests/test_track_internals.py rename to addons/website_event_track/tests/test_track_internals.py diff --git a/addons/website_event_track/views/assets.xml b/addons/website_event_track/views/assets.xml index 120f828839d..c94ee9ece00 100644 --- a/addons/website_event_track/views/assets.xml +++ b/addons/website_event_track/views/assets.xml @@ -5,6 +5,7 @@ + diff --git a/addons/website_event_track/views/event_track_templates_agenda.xml b/addons/website_event_track/views/event_track_templates_agenda.xml index d7ddf00da0b..1608924587c 100644 --- a/addons/website_event_track/views/event_track_templates_agenda.xml +++ b/addons/website_event_track/views/event_track_templates_agenda.xml @@ -150,6 +150,9 @@