From 1163013ed6bf191a9e1548866a4df699f72b5bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 20 Aug 2020 09:16:38 +0000 Subject: [PATCH] [REM] website_event_track_online: 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 remaining content of website event track online to website event track. Agenda notably is completely replaced by the new one developed within track_online module. Update dependencies accordingly. LINKS Task ID-2319779 COM odoo/odoo#56067 ENT odoo/enterprise#12520 UPG odoo/upgrade#1654 --- .../static/src/legacy/public_livechat.js | 2 +- addons/test_event_full/__manifest__.py | 1 - addons/website_event/tests/common.py | 7 + addons/website_event_track/__manifest__.py | 5 + .../controllers/__init__.py | 3 +- .../controllers/event_track.py | 344 +++++++------ .../website_event_track/controllers/main.py | 185 ------- .../controllers/webmanifest.py | 58 +++ .../data/event_track_data.xml | 5 + .../data/event_track_demo.xml | 4 + .../data/event_track_tag_demo.xml | 21 + .../data/event_track_visitor_demo.xml | 0 addons/website_event_track/models/__init__.py | 4 + .../models/event_sponsor.py | 66 ++- .../models/event_sponsor_type.py | 5 + .../website_event_track/models/event_track.py | 192 ++++++- .../models/event_track_stage.py | 8 +- .../models/event_track_tag.py | 4 +- .../models/event_track_tag_category.py} | 8 - .../models/event_track_visitor.py | 0 .../models/website.py | 0 .../models/website_visitor.py | 0 .../security/ir.model.access.csv | 4 + .../static/lib/idb-keyval/idb-keyval.js | 0 .../static/src/js/event_track_reminder.js | 0 .../static/src/js/service_worker.js | 2 +- .../static/src/js/website_event_pwa_widget.js | 4 +- ...bsite_event_track_set_customize_options.js | 94 ---- .../src/scss/event_track_templates.scss | 0 .../static/src/scss/pwa_frontend.scss | 0 .../static/src/xml/customize_options.xml | 23 - .../static/src/xml/website_event_pwa.xml | 0 addons/website_event_track/tests/__init__.py | 1 + .../tests/common.py | 0 addons/website_event_track/views/assets.xml | 10 +- .../views/event_event_views.xml | 6 + .../website_event_track/views/event_menus.xml | 14 + .../views/event_sponsor_views.xml | 32 +- .../views/event_templates.xml} | 4 +- .../views/event_track_stage_views.xml | 4 + .../views/event_track_tag_views.xml | 44 +- .../views/event_track_templates_agenda.xml | 234 ++++++--- .../views/event_track_templates_list.xml | 16 +- .../views/event_track_templates_misc.xml | 35 +- .../views/event_track_templates_page.xml | 16 +- .../views/event_track_templates_reminder.xml | 0 .../views/event_track_views.xml | 468 ++++++++++-------- .../views/event_track_visitor_views.xml | 0 .../views/website_visitor_views.xml | 2 +- .../__manifest__.py | 2 +- .../controllers/exhibitor.py | 4 +- .../tests/test_sponsor_internals.py | 32 +- .../views/event_event_views.xml | 2 +- .../views/event_sponsor_views.xml | 6 +- .../website_event_track_live/__manifest__.py | 2 +- .../controllers/track_live.py | 4 +- addons/website_event_track_online/__init__.py | 6 - .../__manifest__.py | 46 -- .../controllers/__init__.py | 4 - .../data/event_sponsor_demo.xml | 35 -- .../data/event_sponsor_type_data.xml | 14 - .../data/event_track_demo.xml | 90 ---- .../data/event_track_stage_data.xml | 11 - .../data/event_track_tag_demo.xml | 38 -- .../models/__init__.py | 11 - .../models/event_sponsor.py | 62 --- .../models/event_sponsor_type.py | 14 - .../models/event_track.py | 201 -------- .../models/event_track_stage.py | 13 - .../security/ir.model.access.csv | 5 - .../src/js/website_event_customize_options.js | 13 - .../xml/website_event_customize_options.xml | 11 - .../tests/__init__.py | 4 - .../views/assets.xml | 17 - .../views/event_event_views.xml | 20 - .../views/event_menus.xml | 18 - .../views/event_sponsor_type_views.xml | 24 - .../views/event_sponsor_views.xml | 68 --- .../views/event_track_stage_views.xml | 28 -- .../views/event_track_tag_views.xml | 69 --- .../views/event_track_templates.xml | 98 ---- .../views/event_track_templates_agenda.xml | 195 -------- .../views/event_track_views.xml | 75 --- .../views/event_track_visitor_views.xml | 6 +- .../__manifest__.py | 2 +- .../controllers/session.py | 6 +- .../tests/test_track_internals.py | 2 +- .../views/event_track_templates_agenda.xml | 2 +- .../views/event_track_templates_list.xml | 6 +- .../views/event_track_templates_misc.xml | 4 +- .../views/event_track_templates_page.xml | 2 +- 91 files changed, 1207 insertions(+), 2000 deletions(-) rename addons/{website_event_track_online => website_event_track}/controllers/event_track.py (69%) delete mode 100644 addons/website_event_track/controllers/main.py create mode 100644 addons/website_event_track/controllers/webmanifest.py rename addons/{website_event_track_online => website_event_track}/data/event_track_visitor_demo.xml (100%) rename addons/{website_event_track_online/models/event_track_tag.py => website_event_track/models/event_track_tag_category.py} (64%) rename addons/{website_event_track_online => website_event_track}/models/event_track_visitor.py (100%) rename addons/{website_event_track_online => website_event_track}/models/website.py (100%) rename addons/{website_event_track_online => website_event_track}/models/website_visitor.py (100%) rename addons/{website_event_track_online => website_event_track}/static/lib/idb-keyval/idb-keyval.js (100%) rename addons/{website_event_track_online => website_event_track}/static/src/js/event_track_reminder.js (100%) rename addons/{website_event_track_online => website_event_track}/static/src/js/service_worker.js (98%) rename addons/{website_event_track_online => website_event_track}/static/src/js/website_event_pwa_widget.js (97%) delete mode 100644 addons/website_event_track/static/src/js/website_event_track_set_customize_options.js rename addons/{website_event_track_online => website_event_track}/static/src/scss/event_track_templates.scss (100%) rename addons/{website_event_track_online => website_event_track}/static/src/scss/pwa_frontend.scss (100%) delete mode 100644 addons/website_event_track/static/src/xml/customize_options.xml rename addons/{website_event_track_online => website_event_track}/static/src/xml/website_event_pwa.xml (100%) rename addons/{website_event_track_online => website_event_track}/tests/common.py (100%) rename addons/{website_event_track_online/views/event_templates_registration.xml => website_event_track/views/event_templates.xml} (91%) rename addons/{website_event_track_online => website_event_track}/views/event_track_templates_reminder.xml (100%) rename addons/{website_event_track_online => website_event_track}/views/event_track_visitor_views.xml (100%) rename addons/{website_event_track_online => website_event_track}/views/website_visitor_views.xml (96%) delete mode 100644 addons/website_event_track_online/__init__.py delete mode 100644 addons/website_event_track_online/__manifest__.py delete mode 100644 addons/website_event_track_online/controllers/__init__.py delete mode 100644 addons/website_event_track_online/data/event_sponsor_demo.xml delete mode 100644 addons/website_event_track_online/data/event_sponsor_type_data.xml delete mode 100644 addons/website_event_track_online/data/event_track_demo.xml delete mode 100644 addons/website_event_track_online/data/event_track_stage_data.xml delete mode 100644 addons/website_event_track_online/data/event_track_tag_demo.xml delete mode 100644 addons/website_event_track_online/models/__init__.py delete mode 100644 addons/website_event_track_online/models/event_sponsor.py delete mode 100644 addons/website_event_track_online/models/event_sponsor_type.py delete mode 100644 addons/website_event_track_online/models/event_track.py delete mode 100644 addons/website_event_track_online/models/event_track_stage.py delete mode 100644 addons/website_event_track_online/security/ir.model.access.csv delete mode 100644 addons/website_event_track_online/static/src/js/website_event_customize_options.js delete mode 100644 addons/website_event_track_online/static/src/xml/website_event_customize_options.xml delete mode 100644 addons/website_event_track_online/tests/__init__.py delete mode 100644 addons/website_event_track_online/views/assets.xml delete mode 100644 addons/website_event_track_online/views/event_event_views.xml delete mode 100644 addons/website_event_track_online/views/event_menus.xml delete mode 100644 addons/website_event_track_online/views/event_sponsor_type_views.xml delete mode 100644 addons/website_event_track_online/views/event_sponsor_views.xml delete mode 100644 addons/website_event_track_online/views/event_track_stage_views.xml delete mode 100644 addons/website_event_track_online/views/event_track_tag_views.xml delete mode 100644 addons/website_event_track_online/views/event_track_templates.xml delete mode 100644 addons/website_event_track_online/views/event_track_templates_agenda.xml delete mode 100644 addons/website_event_track_online/views/event_track_views.xml diff --git a/addons/im_livechat/static/src/legacy/public_livechat.js b/addons/im_livechat/static/src/legacy/public_livechat.js index 5a29d6e667f..4f14fbb06f7 100644 --- a/addons/im_livechat/static/src/legacy/public_livechat.js +++ b/addons/im_livechat/static/src/legacy/public_livechat.js @@ -111,7 +111,7 @@ var LivechatButton = Widget.extend({ this.$el.css('color', this.options.button_text_color); } - // If website_event_track_online installed, put the livechat banner above the PWA banner. + // If website_event_track installed, put the livechat banner above the PWA banner. var pwaBannerHeight = $('.o_pwa_install_banner').outerHeight(true); if (pwaBannerHeight) { this.$el.css('bottom', pwaBannerHeight + 'px'); diff --git a/addons/test_event_full/__manifest__.py b/addons/test_event_full/__manifest__.py index 363252c06a6..bd0263e341c 100644 --- a/addons/test_event_full/__manifest__.py +++ b/addons/test_event_full/__manifest__.py @@ -20,7 +20,6 @@ automatic lead generation, full Online support, ... 'website_event_meet', 'website_event_sale', 'website_event_track', - 'website_event_track_online', 'website_event_track_exhibitor', 'website_event_track_session', 'website_event_track_live', diff --git a/addons/website_event/tests/common.py b/addons/website_event/tests/common.py index 8b447777d39..1dbf91c8e49 100644 --- a/addons/website_event/tests/common.py +++ b/addons/website_event/tests/common.py @@ -23,6 +23,10 @@ class EventDtPatcher(SavepointCase): 'odoo.addons.event.models.event_event.fields.Datetime', wraps=FieldsDatetime ) + cls.wevent_dt = patch( + 'odoo.addons.website_event.models.event_event.fields.Datetime', + wraps=FieldsDatetime + ) cls.wevent_main_dt = patch( 'odoo.addons.website_event.controllers.main.fields.Datetime', wraps=FieldsDatetime @@ -36,14 +40,17 @@ class EventDtPatcher(SavepointCase): wraps=FieldsDate ) cls.mock_event_dt = cls.event_dt.start() + cls.mock_wevent_dt = cls.wevent_dt.start() cls.mock_wevent_main_dt = cls.wevent_main_dt.start() cls.mock_event_date = cls.event_date.start() cls.mock_wevent_main_date = cls.wevent_main_date.start() cls.mock_event_dt.now.return_value = cls.reference_now + cls.mock_wevent_dt.now.return_value = cls.reference_now cls.mock_wevent_main_dt.now.return_value = cls.reference_now cls.mock_event_date.today.return_value = cls.reference_today cls.mock_wevent_main_date.today.return_value = cls.reference_today cls.addClassCleanup(cls.event_dt.stop) + cls.addClassCleanup(cls.wevent_dt.stop) cls.addClassCleanup(cls.wevent_main_dt.stop) cls.addClassCleanup(cls.event_date.stop) cls.addClassCleanup(cls.wevent_main_date.stop) diff --git a/addons/website_event_track/__manifest__.py b/addons/website_event_track/__manifest__.py index bdcf7efc16c..74618521e33 100644 --- a/addons/website_event_track/__manifest__.py +++ b/addons/website_event_track/__manifest__.py @@ -14,9 +14,11 @@ 'data/event_data.xml', 'data/event_track_data.xml', 'views/assets.xml', + 'views/event_templates.xml', 'views/event_track_templates_agenda.xml', 'views/event_track_templates_list.xml', 'views/event_track_templates_misc.xml', + 'views/event_track_templates_reminder.xml', 'views/event_track_templates_page.xml', 'views/event_track_templates_proposal.xml', 'views/website_templates.xml', @@ -24,9 +26,11 @@ 'views/event_track_location_views.xml', 'views/event_track_tag_views.xml', 'views/event_track_stage_views.xml', + 'views/event_track_visitor_views.xml', 'views/event_sponsor_views.xml', 'views/event_event_views.xml', 'views/event_type_views.xml', + 'views/website_visitor_views.xml', 'views/event_menus.xml', ], 'demo': [ @@ -36,5 +40,6 @@ 'data/event_track_tag_demo.xml', 'data/event_track_demo.xml', 'data/event_track_demo_description.xml', + 'data/event_track_visitor_demo.xml', ], } diff --git a/addons/website_event_track/controllers/__init__.py b/addons/website_event_track/controllers/__init__.py index 5d4b25db9c0..8779baf6109 100644 --- a/addons/website_event_track/controllers/__init__.py +++ b/addons/website_event_track/controllers/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import main +from . import event_track +from . import webmanifest diff --git a/addons/website_event_track_online/controllers/event_track.py b/addons/website_event_track/controllers/event_track.py similarity index 69% rename from addons/website_event_track_online/controllers/event_track.py rename to addons/website_event_track/controllers/event_track.py index 8df8b5deb5c..e338f384580 100644 --- a/addons/website_event_track_online/controllers/event_track.py +++ b/addons/website_event_track/controllers/event_track.py @@ -1,85 +1,71 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -import json - -from werkzeug.exceptions import Forbidden, NotFound from datetime import timedelta +from werkzeug.exceptions import Forbidden, NotFound + +import babel +import babel.dates +import base64 import pytz from odoo import exceptions, http, fields -from odoo.addons.http_routing.models.ir_http import url_for -from odoo.addons.website_event_track.controllers.main import WebsiteEventTrackController from odoo.http import request -from odoo.modules.module import get_module_resource -from odoo.tools import ustr -from odoo.tools.translate import _ +from odoo.tools import plaintext2html, html2plaintext -class EventTrackOnlineController(WebsiteEventTrackController): +class EventTrackController(http.Controller): - def _event_agenda_get_tracks(self, event): - tracks_sudo = event.sudo().track_ids.filtered(lambda track: track.date) - if not request.env.user.has_group('event.group_event_manager'): - tracks_sudo = tracks_sudo.filtered(lambda track: track.is_published or track.stage_id.is_accepted) - return tracks_sudo + # ------------------------------------------------------------ + # TRACK LIST VIEW + # ------------------------------------------------------------ - def time_slot_rounder(self, time, rounded_minutes): - """ Rounds to nearest hour by adding a timedelta hour if minute >= rounded_minutes - E.g. : If rounded_minutes = 15 -> 09:26:00 becomes 09:30:00 - 09:17:00 becomes 09:15:00 - """ - return (time.replace(second=0, microsecond=0, minute=0, hour=time.hour) - + timedelta(minutes=rounded_minutes * (time.minute // rounded_minutes))) + @http.route([ + '''/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): + raise NotFound() - def _split_track_by_days(self, track, local_tz): - """ - Based on the track start_date and the duration, - split the track duration into : - start_time by day : number of time slot (15 minutes) that the track takes on that day. - E.g. : start date = 01-01-2000 10:00 PM and duration = 3 hours - return { - 01-01-2000 10:00:00 PM: 8 (2 * 4), - 01-02-2000 00:00:00 AM: 4 (1 * 4) - } - Also return a set of all the time slots - """ - start_date = fields.Datetime.from_string(track.date).replace(tzinfo=pytz.utc).astimezone(local_tz) - start_datetime = self.time_slot_rounder(start_date, 15) - end_datetime = self.time_slot_rounder(start_datetime + timedelta(hours=(track.duration or 0.25)), 15) - time_slots_count = int(((end_datetime - start_datetime).total_seconds() / 3600) * 4) + 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 - time_slots_by_day_start_time = {start_datetime: 0} - for i in range(0, time_slots_count): - # If the new time slot is still on the current day - next_day = (start_datetime + timedelta(days=1)).date() - if (start_datetime + timedelta(minutes=15*i)).date() <= next_day: - time_slots_by_day_start_time[start_datetime] += 1 - else: - start_datetime = next_day.datetime() - time_slots_by_day_start_time[start_datetime] = 0 + values = { + 'event': event, + 'main_object': event, + 'tracks': tracks, + 'tags': event.tracks_tag_ids, + 'searches': searches, + 'html2plaintext': html2plaintext + } + return request.render("website_event_track.tracks", values) - return time_slots_by_day_start_time + # ------------------------------------------------------------ + # AGENDA VIEW + # ------------------------------------------------------------ - def _get_occupied_cells(self, track, rowspan, locations, local_tz): - """ - In order to use only once the cells that the tracks will occupy, we need to reserve those cells - (time_slot, location) coordinate. Those coordinated will be given to the template to avoid adding - blank cells where already occupied by a track. - """ - occupied_cells = [] + @http.route(['''/event//agenda'''], type='http', auth="public", website=True, sitemap=False) + def event_agenda(self, event, tag=None, **post): + if not event.can_access_from_current_website(): + raise NotFound() - start_date = fields.Datetime.from_string(track.date).replace(tzinfo=pytz.utc).astimezone(local_tz) - start_date = self.time_slot_rounder(start_date, 15) - for i in range(0, rowspan): - time_slot = start_date + timedelta(minutes=15*i) - if track.location_id: - occupied_cells.append((time_slot, track.location_id)) - # when no location, reserve all locations - else: - occupied_cells += [(time_slot, location) for location in locations if location] + event = event.with_context(tz=event.date_tz or 'UTC') + vals = { + 'event': event, + 'main_object': event, + 'tag': tag, + 'user_event_manager': request.env.user.has_group('event.group_event_manager'), + } - return occupied_cells + vals.update(self._prepare_calendar_values(event)) + + return request.render("website_event_track.agenda_online", vals) def _prepare_calendar_values(self, event): """ @@ -157,48 +143,90 @@ class EventTrackOnlineController(WebsiteEventTrackController): 'locations': locations } - @http.route(['''/event//agenda'''], type='http', auth="public", website=True, sitemap=False) - def event_agenda(self, event, tag=None, **post): + def _event_agenda_get_tracks(self, event): + tracks_sudo = event.sudo().track_ids.filtered(lambda track: track.date) + if not request.env.user.has_group('event.group_event_manager'): + tracks_sudo = tracks_sudo.filtered(lambda track: track.is_published or track.stage_id.is_accepted) + return tracks_sudo + + def _get_locale_time(self, dt_time, lang_code): + """ Get locale time from datetime object + + :param dt_time: datetime object + :param lang_code: language code (eg. en_US) + """ + locale = babel.Locale.parse(lang_code) + return babel.dates.format_time(dt_time, format='short', locale=locale) + + def time_slot_rounder(self, time, rounded_minutes): + """ Rounds to nearest hour by adding a timedelta hour if minute >= rounded_minutes + E.g. : If rounded_minutes = 15 -> 09:26:00 becomes 09:30:00 + 09:17:00 becomes 09:15:00 + """ + return (time.replace(second=0, microsecond=0, minute=0, hour=time.hour) + + timedelta(minutes=rounded_minutes * (time.minute // rounded_minutes))) + + def _split_track_by_days(self, track, local_tz): + """ + Based on the track start_date and the duration, + split the track duration into : + start_time by day : number of time slot (15 minutes) that the track takes on that day. + E.g. : start date = 01-01-2000 10:00 PM and duration = 3 hours + return { + 01-01-2000 10:00:00 PM: 8 (2 * 4), + 01-02-2000 00:00:00 AM: 4 (1 * 4) + } + Also return a set of all the time slots + """ + start_date = fields.Datetime.from_string(track.date).replace(tzinfo=pytz.utc).astimezone(local_tz) + start_datetime = self.time_slot_rounder(start_date, 15) + end_datetime = self.time_slot_rounder(start_datetime + timedelta(hours=(track.duration or 0.25)), 15) + time_slots_count = int(((end_datetime - start_datetime).total_seconds() / 3600) * 4) + + time_slots_by_day_start_time = {start_datetime: 0} + for i in range(0, time_slots_count): + # If the new time slot is still on the current day + next_day = (start_datetime + timedelta(days=1)).date() + if (start_datetime + timedelta(minutes=15*i)).date() <= next_day: + time_slots_by_day_start_time[start_datetime] += 1 + else: + start_datetime = next_day.datetime() + time_slots_by_day_start_time[start_datetime] = 0 + + return time_slots_by_day_start_time + + def _get_occupied_cells(self, track, rowspan, locations, local_tz): + """ + In order to use only once the cells that the tracks will occupy, we need to reserve those cells + (time_slot, location) coordinate. Those coordinated will be given to the template to avoid adding + blank cells where already occupied by a track. + """ + occupied_cells = [] + + start_date = fields.Datetime.from_string(track.date).replace(tzinfo=pytz.utc).astimezone(local_tz) + start_date = self.time_slot_rounder(start_date, 15) + for i in range(0, rowspan): + time_slot = start_date + timedelta(minutes=15*i) + if track.location_id: + occupied_cells.append((time_slot, track.location_id)) + # when no location, reserve all locations + else: + occupied_cells += [(time_slot, location) for location in locations if location] + + return occupied_cells + + # ------------------------------------------------------------ + # TRACK PAGE VIEW + # ------------------------------------------------------------ + + @http.route(['''/event//track//track_proposal'''], type='http', auth="public", website=True, sitemap=False) + def event_track_proposal(self, event, **post): + if not event.can_access_from_current_website(): + raise NotFound() + + return request.render("website_event_track.event_track_proposal", {'event': event, 'main_object': event}) + + @http.route(['''/event//track_proposal/post'''], type='http', auth="public", methods=['POST'], website=True) + def event_track_proposal_post(self, event, **post): + if not event.can_access_from_current_website(): + raise NotFound() + + tags = [] + for tag in event.allowed_track_tag_ids: + if post.get('tag_' + str(tag.id)): + tags.append(tag.id) + + track = request.env['event.track'].sudo().create({ + 'name': post['track_name'], + 'partner_name': post['partner_name'], + 'partner_email': post['email_from'], + 'partner_phone': post['phone'], + 'partner_biography': plaintext2html(post['biography']), + 'event_id': event.id, + 'tag_ids': [(6, 0, tags)], + 'user_id': False, + 'description': plaintext2html(post['description']), + 'image': base64.b64encode(post['image'].read()) if post.get('image') else False + }) + if request.env.user != request.website.user_id: + track.sudo().message_subscribe(partner_ids=request.env.user.partner_id.ids) + else: + partner = request.env['res.partner'].sudo().search([('email', '=', post['email_from'])]) + if partner: + track.sudo().message_subscribe(partner_ids=partner.ids) + return request.render("website_event_track.event_track_proposal", {'track': track, 'event': event}) + + # ------------------------------------------------------------ + # TOOLS + # ------------------------------------------------------------ + + def _fetch_track(self, track_id, allow_is_accepted=False): + track = request.env['event.track'].browse(track_id).exists() + if not track: + raise NotFound() + try: + track.check_access_rights('read') + track.check_access_rule('read') + except exceptions.AccessError: + track_sudo = track.sudo() + if allow_is_accepted and track_sudo.is_accepted: + track = track_sudo + else: + raise Forbidden() + + event = track.event_id + # JSON RPC have no website in requests + if hasattr(request, 'website_id') and not event.can_access_from_current_website(): + raise NotFound() + try: + event.check_access_rights('read') + event.check_access_rule('read') + except exceptions.AccessError: + raise Forbidden() + + return track diff --git a/addons/website_event_track/controllers/main.py b/addons/website_event_track/controllers/main.py deleted file mode 100644 index 13477d422b4..00000000000 --- a/addons/website_event_track/controllers/main.py +++ /dev/null @@ -1,185 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import babel -import babel.dates -import collections -import datetime -import pytz -import base64 -from werkzeug.exceptions import NotFound - -from odoo import fields, http -from odoo.http import request -from odoo.tools import plaintext2html, html2plaintext - - -class WebsiteEventTrackController(http.Controller): - - # ------------------------------------------------------------ - # TRACK LIST VIEW - # ------------------------------------------------------------ - - @http.route([ - '''/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): - 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 - - values = { - 'event': event, - 'main_object': event, - 'tracks': tracks, - 'tags': event.tracks_tag_ids, - 'searches': searches, - 'html2plaintext': html2plaintext - } - return request.render("website_event_track.tracks", values) - - # ------------------------------------------------------------ - # AGENDA VIEW - # ------------------------------------------------------------ - - def _get_locale_time(self, dt_time, lang_code): - """ Get locale time from datetime object - - :param dt_time: datetime object - :param lang_code: language code (eg. en_US) - """ - locale = babel.Locale.parse(lang_code) - return babel.dates.format_time(dt_time, format='short', locale=locale) - - def _prepare_calendar(self, event, event_track_ids): - local_tz = pytz.timezone(event.date_tz or 'UTC') - lang_code = request.env.context.get('lang') - locations = {} # { location: [track, start_date, end_date, rowspan]} - dates = [] # [ (date, {}) ] - for track in event_track_ids: - locations.setdefault(track.location_id or False, []) - - forcetr = True - for track in event_track_ids: - start_date = fields.Datetime.from_string(track.date).replace(tzinfo=pytz.utc).astimezone(local_tz) - end_date = start_date + datetime.timedelta(hours=(track.duration or 0.5)) - location = track.location_id or False - locations.setdefault(location, []) - - # New TR, align all events - if forcetr or (start_date>dates[-1][0]) or not location: - formatted_time = self._get_locale_time(start_date, lang_code) - dates.append((start_date, {}, bool(location), formatted_time)) - for loc in list(locations): - if locations[loc] and (locations[loc][-1][2] > start_date): - locations[loc][-1][3] += 1 - elif not locations[loc] or locations[loc][-1][2] <= start_date: - locations[loc].append([False, locations[loc] and locations[loc][-1][2] or dates[0][0], start_date, 1]) - dates[-1][1][loc] = locations[loc][-1] - forcetr = not bool(location) - - # Add event - if locations[location] and locations[location][-1][1] > start_date: - locations[location][-1][3] -= 1 - locations[location].append([track, start_date, end_date, 1]) - dates[-1][1][location] = locations[location][-1] - locations = collections.OrderedDict(sorted(locations.items(), key=lambda t: t[0].id if t[0] else 0)) - return { - 'locations': locations, - 'dates': dates - } - - @http.route(['''/event//agenda'''], type='http', auth="public", website=True, sitemap=False) - def event_agenda(self, event, tag=None, **post): - if not event.can_access_from_current_website(): - raise NotFound() - - event = event.with_context(tz=event.date_tz or 'UTC') - local_tz = pytz.timezone(event.date_tz or 'UTC') - days_tracks = collections.defaultdict(lambda: []) - event_track_ids = self._event_agenda_get_tracks(event) - for track in event_track_ids.sorted(lambda track: (bool(track.date), track.date, bool(track.location_id))): - if not track.date: - continue - date = fields.Datetime.from_string(track.date).replace(tzinfo=pytz.utc).astimezone(local_tz) - days_tracks[str(date)[:10]].append(track) - - days = {} - tracks_by_days = {} - for day, tracks in days_tracks.items(): - tracks_by_days[day] = tracks - days[day] = self._prepare_calendar(event, tracks) - - return request.render("website_event_track.agenda", { - 'event': event, - 'main_object': event, - 'days': days, - 'tracks_by_days': tracks_by_days, - 'tag': tag, - 'user_event_manager': request.env.user.has_group('event.group_event_manager'), - }) - - def _event_agenda_get_tracks(self, event): - return event.track_ids - - # ------------------------------------------------------------ - # TRACK PAGE VIEW - # ------------------------------------------------------------ - - @http.route(['''/event//track//track_proposal'''], type='http', auth="public", website=True, sitemap=False) - def event_track_proposal(self, event, **post): - if not event.can_access_from_current_website(): - raise NotFound() - - return request.render("website_event_track.event_track_proposal", {'event': event, 'main_object': event}) - - @http.route(['''/event//track_proposal/post'''], type='http', auth="public", methods=['POST'], website=True) - def event_track_proposal_post(self, event, **post): - if not event.can_access_from_current_website(): - raise NotFound() - - tags = [] - for tag in event.allowed_track_tag_ids: - if post.get('tag_' + str(tag.id)): - tags.append(tag.id) - - track = request.env['event.track'].sudo().create({ - 'name': post['track_name'], - 'partner_name': post['partner_name'], - 'partner_email': post['email_from'], - 'partner_phone': post['phone'], - 'partner_biography': plaintext2html(post['biography']), - 'event_id': event.id, - 'tag_ids': [(6, 0, tags)], - 'user_id': False, - 'description': plaintext2html(post['description']), - 'image': base64.b64encode(post['image'].read()) if post.get('image') else False - }) - if request.env.user != request.website.user_id: - track.sudo().message_subscribe(partner_ids=request.env.user.partner_id.ids) - else: - partner = request.env['res.partner'].sudo().search([('email', '=', post['email_from'])]) - if partner: - track.sudo().message_subscribe(partner_ids=partner.ids) - return request.render("website_event_track.event_track_proposal", {'track': track, 'event': event}) diff --git a/addons/website_event_track/controllers/webmanifest.py b/addons/website_event_track/controllers/webmanifest.py new file mode 100644 index 00000000000..a777ff10303 --- /dev/null +++ b/addons/website_event_track/controllers/webmanifest.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import json +import pytz + +from odoo import http +from odoo.addons.http_routing.models.ir_http import url_for +from odoo.http import request +from odoo.modules.module import get_module_resource +from odoo.tools import ustr +from odoo.tools.translate import _ + + +class TrackManifest(http.Controller): + + @http.route('/event/manifest.webmanifest', type='http', auth='public', methods=['GET'], website=True, sitemap=False) + def webmanifest(self): + """ Returns a WebManifest describing the metadata associated with a web application. + Using this metadata, user agents can provide developers with means to create user + experiences that are more comparable to that of a native application. + """ + company = request.env.company + website = request.website + manifest = { + 'name': _('%s Online Events') % company.name, + 'short_name': company.name, + 'description': _('%s Online Events') % company.name, + 'scope': url_for('/event'), + 'start_url': url_for('/event'), + 'display': 'standalone', + 'background_color': '#ffffff', + 'theme_color': '#875A7B', + } + icon_sizes = ['192x192', '512x512'] + manifest['icons'] = [{ + 'src': website.image_url(website, 'app_icon', size=size), + 'sizes': size, + 'type': 'image/png', + } for size in icon_sizes] + body = json.dumps(manifest, default=ustr) + response = request.make_response(body, [ + ('Content-Type', 'application/manifest+json'), + ]) + return response + + @http.route('/event/service-worker.js', type='http', auth='public', methods=['GET'], website=True, sitemap=False) + def service_worker(self): + """ Returns a ServiceWorker javascript file scoped for website_event + """ + sw_file = get_module_resource('website_event_track', 'static/src/js/service_worker.js') + with open(sw_file, 'rb') as fp: + body = fp.read() + response = request.make_response(body, [ + ('Content-Type', 'text/javascript'), + ('Service-Worker-Allowed', url_for('/event')), + ]) + return response diff --git a/addons/website_event_track/data/event_track_data.xml b/addons/website_event_track/data/event_track_data.xml index 707cb8255d2..33cf0fb5fad 100644 --- a/addons/website_event_track/data/event_track_data.xml +++ b/addons/website_event_track/data/event_track_data.xml @@ -33,14 +33,17 @@ Bronze 3 + Bronze Silver 2 + Silver Gold 1 + Gold @@ -58,11 +61,13 @@ Announced 3 3 + Published 4 4 + diff --git a/addons/website_event_track/data/event_track_demo.xml b/addons/website_event_track/data/event_track_demo.xml index 62e6f0a7af5..4eeead8b65f 100644 --- a/addons/website_event_track/data/event_track_demo.xml +++ b/addons/website_event_track/data/event_track_demo.xml @@ -333,6 +333,7 @@ 1 + 1 + 2 + @@ -572,6 +575,7 @@ 7 + diff --git a/addons/website_event_track/data/event_track_tag_demo.xml b/addons/website_event_track/data/event_track_tag_demo.xml index 1d4b4234e3e..bffa1d6cd05 100644 --- a/addons/website_event_track/data/event_track_tag_demo.xml +++ b/addons/website_event_track/data/event_track_tag_demo.xml @@ -1,28 +1,49 @@ + + Audience + 10 + + + Format + 20 + + Consumers 1 + + 1 Sales 2 + + 2 Research 3 + + 3 Lightning Talks 4 + + 11 Round Table 5 + + 12 Keynote 6 + + 13 diff --git a/addons/website_event_track_online/data/event_track_visitor_demo.xml b/addons/website_event_track/data/event_track_visitor_demo.xml similarity index 100% rename from addons/website_event_track_online/data/event_track_visitor_demo.xml rename to addons/website_event_track/data/event_track_visitor_demo.xml diff --git a/addons/website_event_track/models/__init__.py b/addons/website_event_track/models/__init__.py index eb261d9cca2..a9c5599582c 100644 --- a/addons/website_event_track/models/__init__.py +++ b/addons/website_event_track/models/__init__.py @@ -8,6 +8,10 @@ from . import event_track from . import event_track_location from . import event_track_stage from . import event_track_tag +from . import event_track_tag_category +from . import event_track_visitor from . import event_type +from . import website from . import website_event_menu from . import website_menu +from . import website_visitor diff --git a/addons/website_event_track/models/event_sponsor.py b/addons/website_event_track/models/event_sponsor.py index 5966d183dec..0fca8a53a07 100644 --- a/addons/website_event_track/models/event_sponsor.py +++ b/addons/website_event_track/models/event_sponsor.py @@ -2,27 +2,40 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import api, fields, models, _ +from odoo.modules.module import get_resource_path class Sponsor(models.Model): _name = "event.sponsor" _description = 'Event Sponsor' _order = "sequence, sponsor_type_id" - _rec_name = 'partner_name' + _rec_name = 'name' _inherit = ['mail.thread', 'mail.activity.mixin'] event_id = fields.Many2one('event.event', 'Event', required=True) sponsor_type_id = fields.Many2one('event.sponsor.type', 'Sponsoring Type', required=True) + url = fields.Char('Sponsor Website', compute='_compute_url', readonly=False, store=True) + sequence = fields.Integer('Sequence') + active = fields.Boolean(default=True) + # contact information partner_id = fields.Many2one('res.partner', 'Sponsor/Customer', required=True) partner_name = fields.Char('Name', related='partner_id.name') partner_email = fields.Char('Email', related='partner_id.email') partner_phone = fields.Char('Phone', related='partner_id.phone') partner_mobile = fields.Char('Mobile', related='partner_id.mobile') - url = fields.Char('Sponsor Website', compute='_compute_url', readonly=False, store=True) - sequence = fields.Integer('Sequence') - image_128 = fields.Image( - string="Logo", related='partner_id.image_128', store=True, readonly=False) - active = fields.Boolean(default=True) + name = fields.Char('Sponsor Name', compute='_compute_name', readonly=False, store=True) + email = fields.Char('Sponsor Email', compute='_compute_email', readonly=False, store=True) + phone = fields.Char('Sponsor Phone', compute='_compute_phone', readonly=False, store=True) + mobile = fields.Char('Sponsor Mobile', compute='_compute_mobile', readonly=False, store=True) + # image + image_512 = fields.Image( + string="Logo", max_width=512, max_height=512, + compute='_compute_image_512', readonly=False, store=True) + image_256 = fields.Image("Image 256", related="image_512", max_width=256, max_height=256, store=False) + image_128 = fields.Image("Image 128", related="image_512", max_width=128, max_height=128, store=False) + website_image_url = fields.Char( + string='Image URL', max_width=256, max_height=256, + compute='_compute_website_image_url', compute_sudo=True, store=False) @api.depends('partner_id') def _compute_url(self): @@ -30,6 +43,47 @@ class Sponsor(models.Model): if sponsor.partner_id.website or not sponsor.url: sponsor.url = sponsor.partner_id.website + @api.depends('partner_id') + def _compute_name(self): + self._synchronize_with_partner('name') + + @api.depends('partner_id') + def _compute_email(self): + self._synchronize_with_partner('email') + + @api.depends('partner_id') + def _compute_phone(self): + self._synchronize_with_partner('phone') + + @api.depends('partner_id') + def _compute_mobile(self): + self._synchronize_with_partner('mobile') + + @api.depends('partner_id') + def _compute_image_512(self): + self._synchronize_with_partner('image_512') + + @api.depends('image_256', 'partner_id.image_256') + def _compute_website_image_url(self): + for sponsor in self: + if sponsor.image_256: + sponsor.website_image_url = self.env['website'].image_url(sponsor, 'image_256', size=256) + elif sponsor.partner_id.image_256: + sponsor.website_image_url = self.env['website'].image_url(sponsor.partner_id, 'image_256', size=256) + else: + sponsor.website_image_url = get_resource_path('website_event_track', 'static/src/img', 'event_sponsor_default_%d.png' % (sponsor.id % 1)) + + def _synchronize_with_partner(self, fname): + """ Synchronize with partner if not set. Setting a value does not write + on partner as this may be event-specific information. """ + for sponsor in self: + if not sponsor[fname]: + sponsor[fname] = sponsor.partner_id[fname] + + # ------------------------------------------------------------ + # MESSAGING + # ------------------------------------------------------------ + def _message_get_suggested_recipients(self): recipients = super(Sponsor, self)._message_get_suggested_recipients() for sponsor in self: diff --git a/addons/website_event_track/models/event_sponsor_type.py b/addons/website_event_track/models/event_sponsor_type.py index 3d3cd5749c8..8eb3eb71fbd 100644 --- a/addons/website_event_track/models/event_sponsor_type.py +++ b/addons/website_event_track/models/event_sponsor_type.py @@ -11,3 +11,8 @@ class SponsorType(models.Model): name = fields.Char('Sponsor Type', required=True, translate=True) sequence = fields.Integer('Sequence') + display_ribbon_style = fields.Selection([ + ('no_ribbon', 'No Ribbon'), + ('Gold', 'Gold'), + ('Silver', 'Silver'), + ('Bronze', 'Bronze')], string='Ribbon Style') diff --git a/addons/website_event_track/models/event_track.py b/addons/website_event_track/models/event_track.py index 04d2d9e6514..2ec57bddde4 100644 --- a/addons/website_event_track/models/event_track.py +++ b/addons/website_event_track/models/event_track.py @@ -4,8 +4,10 @@ from datetime import timedelta from odoo import api, fields, models -from odoo.tools.translate import _, html_translate from odoo.addons.http_routing.models.ir_http import slug +from odoo.osv import expression +from odoo.tools.mail import is_html_empty +from odoo.tools.translate import _, html_translate class Track(models.Model): @@ -21,7 +23,6 @@ class Track(models.Model): # description name = fields.Char('Title', required=True, translate=True) event_id = fields.Many2one('event.event', 'Event', required=True) - image = fields.Image("Image", max_width=128, max_height=128) active = fields.Boolean(default=True) user_id = fields.Many2one('res.users', 'Responsible', tracking=True, default=lambda self: self.env.user) company_id = fields.Many2one('res.company', related='event_id.company_id') @@ -38,6 +39,7 @@ class Track(models.Model): index=True, copy=False, default=_get_default_stage_id, group_expand='_read_group_stage_ids', required=True, tracking=True) + is_accepted = fields.Boolean('Is Accepted', related='stage_id.is_accepted', readonly=True) kanban_state = fields.Selection([ ('normal', 'Grey'), ('done', 'Green'), @@ -58,12 +60,46 @@ class Track(models.Model): partner_phone = fields.Char( string='Phone', compute='_compute_partner_info', readonly=False, store=True, tracking=30) - partner_biography = fields.Html(string='Biography') + partner_biography = fields.Html( + string='Biography', compute='_compute_partner_biography', + readonly=False, store=True) + partner_function = fields.Char( + 'Job Position', related='partner_id.function', + compute_sudo=True, readonly=True) + partner_company_name = fields.Char( + 'Company Name', related='partner_id.parent_name', + compute_sudo=True, readonly=True) + image = fields.Image( + string="Speaker Photo", compute="_compute_speaker_image", + readonly=False, store=True, + max_width=256, max_height=256) location_id = fields.Many2one('event.track.location', 'Location') # time information 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.") + # frontend description + website_image = fields.Image(string="Website Image", max_width=1024, max_height=1024) + website_image_url = fields.Char( + string='Image URL', compute='_compute_website_image_url', + compute_sudo=True, store=False) + # wishlist / visitors management + event_track_visitor_ids = fields.One2many( + 'event.track.visitor', 'track_id', string="Track Visitors", + groups="event.group_event_user") + is_reminder_on = fields.Boolean('Is Reminder On', compute='_compute_is_reminder_on') + wishlist_visitor_ids = fields.Many2many( + 'website.visitor', string="Visitor Wishlist", + compute="_compute_wishlist_visitor_ids", compute_sudo=True, + search="_search_wishlist_visitor_ids", + groups="event.group_event_user") + wishlist_visitor_count = fields.Integer( + string="# Wishlisted", + compute="_compute_wishlist_visitor_ids", compute_sudo=True, + groups="event.group_event_user") + 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.""") @api.depends('name') def _compute_website_url(self): @@ -72,6 +108,8 @@ class Track(models.Model): if track.id: track.website_url = '/event/%s/track/%s' % (slug(track.event_id), slug(track)) + # SPEAKER + @api.depends('partner_id') def _compute_partner_info(self): for track in self: @@ -80,6 +118,23 @@ class Track(models.Model): track.partner_email = track.partner_id.email track.partner_phone = track.partner_id.phone + @api.depends('partner_id') + def _compute_partner_biography(self): + for track in self: + if not track.partner_biography: + track.partner_biography = track.partner_id.website_description + elif track.partner_id and is_html_empty(track.partner_biography) and \ + not is_html_empty(track.partner_id.website_description): + track.partner_biography = track.partner_id.website_description + + @api.depends('partner_id') + def _compute_speaker_image(self): + for track in self: + if not track.image: + track.image = track.partner_id.image_256 + + # TIME + @api.depends('date', 'duration') def _compute_end_date(self): for track in self: @@ -89,6 +144,85 @@ class Track(models.Model): else: track.date_end = False + + # FRONTEND DESCRIPTION + + @api.depends('image', 'partner_id.image_256') + def _compute_website_image_url(self): + for track in self: + if track.website_image: + track.website_image_url = self.env['website'].image_url(track, 'website_image', size=1024) + else: + track.website_image_url = '/website_event_track/static/src/img/event_track_default_%d.jpeg' % (track.id % 2) + + # WISHLIST / VISITOR MANAGEMENT + + @api.depends('wishlisted_by_default', 'event_track_visitor_ids.visitor_id', + 'event_track_visitor_ids.partner_id', 'event_track_visitor_ids.is_wishlisted', + 'event_track_visitor_ids.is_blacklisted') + @api.depends_context('uid') + def _compute_is_reminder_on(self): + current_visitor = self.env['website.visitor']._get_visitor_from_request(force_create=False) + if self.env.user._is_public() and not current_visitor: + for track in self: + track.is_reminder_on = track.wishlisted_by_default + else: + if self.env.user._is_public(): + domain = [('visitor_id', '=', current_visitor.id)] + elif current_visitor: + domain = [ + '|', + ('partner_id', '=', self.env.user.partner_id.id), + ('visitor_id', '=', current_visitor.id) + ] + else: + domain = [('partner_id', '=', self.env.user.partner_id.id)] + + event_track_visitors = self.env['event.track.visitor'].sudo().search_read( + expression.AND([ + domain, + [('track_id', 'in', self.ids)] + ]), fields=['track_id', 'is_wishlisted', 'is_blacklisted'] + ) + + wishlist_map = { + track_visitor['track_id'][0]: { + 'is_wishlisted': track_visitor['is_wishlisted'], + 'is_blacklisted': track_visitor['is_blacklisted'] + } for track_visitor in event_track_visitors + } + for track in self: + if wishlist_map.get(track.id): + track.is_reminder_on = wishlist_map.get(track.id)['is_wishlisted'] or (track.wishlisted_by_default and not wishlist_map[track.id]['is_blacklisted']) + else: + track.is_reminder_on = track.wishlisted_by_default + + @api.depends('event_track_visitor_ids.visitor_id', 'event_track_visitor_ids.is_wishlisted') + def _compute_wishlist_visitor_ids(self): + results = self.env['event.track.visitor'].read_group( + [('track_id', 'in', self.ids), ('is_wishlisted', '=', True)], + ['track_id', 'visitor_id:array_agg'], + ['track_id'] + ) + visitor_ids_map = {result['track_id'][0]: result['visitor_id'] for result in results} + for track in self: + track.wishlist_visitor_ids = visitor_ids_map.get(track.id, []) + track.wishlist_visitor_count = len(visitor_ids_map.get(track.id, [])) + + def _search_wishlist_visitor_ids(self, operator, operand): + if operator == "not in": + raise NotImplementedError("Unsupported 'Not In' operation on track wishlist visitors") + + track_visitors = self.env['event.track.visitor'].sudo().search([ + ('visitor_id', operator, operand), + ('is_wishlisted', '=', True) + ]) + return [('id', 'in', track_visitors.track_id.ids)] + + # ------------------------------------------------------------ + # CRUD + # ------------------------------------------------------------ + @api.model_create_multi def create(self, vals_list): tracks = super(Track, self).create(vals_list) @@ -100,12 +234,16 @@ class Track(models.Model): subject=track.name, subtype_id=self.env.ref('website_event_track.mt_event_track').id, ) + track._synchronize_with_stage(track.stage_id) return tracks def write(self, vals): if 'stage_id' in vals and 'kanban_state' not in vals: vals['kanban_state'] = 'normal' + if vals.get('stage_id'): + stage = self.env['event.track.stage'].browse(vals['stage_id']) + self._synchronize_with_stage(stage) res = super(Track, self).write(vals) if vals.get('partner_id'): self.message_subscribe([vals['partner_id']]) @@ -116,6 +254,14 @@ class Track(models.Model): """ Always display all stages """ return stages.search([], order=order) + def _synchronize_with_stage(self, stage): + if stage.is_done: + self.is_published = True + + # ------------------------------------------------------------ + # MESSAGING + # ------------------------------------------------------------ + def _track_template(self, changes): res = super(Track, self)._track_template(changes) track = self[0] @@ -157,6 +303,10 @@ class Track(models.Model): ]).write({'partner_id': new_partner.id}) return super(Track, self)._message_post_after_hook(message, msg_vals) + # ------------------------------------------------------------ + # ACTION + # ------------------------------------------------------------ + def open_track_speakers_list(self): return { 'name': _('Speakers'), @@ -166,3 +316,39 @@ class Track(models.Model): 'view_id': False, 'type': 'ir.actions.act_window', } + + # ------------------------------------------------------------ + # TOOLS + # ------------------------------------------------------------ + + def _get_event_track_visitors(self, force_create=False): + self.ensure_one() + + force_visitor_create = self.env.user._is_public() + visitor_sudo = self.env['website.visitor']._get_visitor_from_request(force_create=force_visitor_create) + if visitor_sudo: + visitor_sudo._update_visitor_last_visit() + + if self.env.user._is_public(): + domain = [('visitor_id', '=', visitor_sudo.id)] + elif visitor_sudo: + domain = [ + '|', + ('partner_id', '=', self.env.user.partner_id.id), + ('visitor_id', '=', visitor_sudo.id) + ] + else: + domain = [('partner_id', '=', self.env.user.partner_id.id)] + + track_visitors = self.env['event.track.visitor'].sudo().search( + expression.AND([domain, [('track_id', 'in', self.ids)]]) + ) + missing = self - track_visitors.track_id + if missing and force_create: + track_visitors += self.env['event.track.visitor'].sudo().create([{ + 'visitor_id': visitor_sudo.id, + 'partner_id': self.env.user.partner_id.id if not self.env.user._is_public() else False, + 'track_id': track.id, + } for track in missing]) + + return track_visitors diff --git a/addons/website_event_track/models/event_track_stage.py b/addons/website_event_track/models/event_track_stage.py index 022237db4f5..9e1a177c0f3 100644 --- a/addons/website_event_track/models/event_track_stage.py +++ b/addons/website_event_track/models/event_track_stage.py @@ -18,6 +18,12 @@ class TrackStage(models.Model): fold = fields.Boolean( string='Folded in Kanban', help='This stage is folded in the kanban view when there are no records in that stage to display.') - is_done = fields.Boolean(string='Done Stage') + is_accepted = fields.Boolean( + string='Accepted Stage', + help='Accepted tracks are displayed in agenda views but not accessible.') + is_done = fields.Boolean( + string='Done Stage', + help='Done tracks are automatically published so that they are available in frontend.') is_cancel = fields.Boolean(string='Canceled Stage') + is_done = fields.Boolean() color = fields.Integer(string='Color') diff --git a/addons/website_event_track/models/event_track_tag.py b/addons/website_event_track/models/event_track_tag.py index 997a2c72f46..6efcba1e138 100644 --- a/addons/website_event_track/models/event_track_tag.py +++ b/addons/website_event_track/models/event_track_tag.py @@ -9,7 +9,7 @@ from odoo import fields, models class TrackTag(models.Model): _name = "event.track.tag" _description = 'Event Track Tag' - _order = 'name' + _order = "category_id, sequence, name" def _default_color(self): return randint(1, 11) @@ -19,6 +19,8 @@ class TrackTag(models.Model): color = fields.Integer( string='Color Index', default=lambda self: self._default_color(), help="Note that colorless tags won't be available on the website.") + sequence = fields.Integer('Sequence', default=10) + category_id = fields.Many2one('event.track.tag.category', string="Category", ondelete="set null") _sql_constraints = [ ('name_uniq', 'unique (name)', "Tag name already exists !"), diff --git a/addons/website_event_track_online/models/event_track_tag.py b/addons/website_event_track/models/event_track_tag_category.py similarity index 64% rename from addons/website_event_track_online/models/event_track_tag.py rename to addons/website_event_track/models/event_track_tag_category.py index b04fb7bf251..05f14e7abed 100644 --- a/addons/website_event_track_online/models/event_track_tag.py +++ b/addons/website_event_track/models/event_track_tag_category.py @@ -12,11 +12,3 @@ class TrackTagCategory(models.Model): name = fields.Char("Name", required=True, translate=True) sequence = fields.Integer('Sequence', default=10) tag_ids = fields.One2many('event.track.tag', 'category_id', string="Tags") - - -class TrackTag(models.Model): - _inherit = "event.track.tag" - _order = "category_id, sequence, name" - - sequence = fields.Integer('Sequence', default=10) - category_id = fields.Many2one('event.track.tag.category', string="Category", ondelete="set null") diff --git a/addons/website_event_track_online/models/event_track_visitor.py b/addons/website_event_track/models/event_track_visitor.py similarity index 100% rename from addons/website_event_track_online/models/event_track_visitor.py rename to addons/website_event_track/models/event_track_visitor.py diff --git a/addons/website_event_track_online/models/website.py b/addons/website_event_track/models/website.py similarity index 100% rename from addons/website_event_track_online/models/website.py rename to addons/website_event_track/models/website.py diff --git a/addons/website_event_track_online/models/website_visitor.py b/addons/website_event_track/models/website_visitor.py similarity index 100% rename from addons/website_event_track_online/models/website_visitor.py rename to addons/website_event_track/models/website_visitor.py diff --git a/addons/website_event_track/security/ir.model.access.csv b/addons/website_event_track/security/ir.model.access.csv index 9a52bad509d..0cc16c6a31b 100644 --- a/addons/website_event_track/security/ir.model.access.csv +++ b/addons/website_event_track/security/ir.model.access.csv @@ -12,3 +12,7 @@ access_event_track_sponsor_manager,event.track.sponsor.type.public,model_event_s access_event_track_sponsor_public,event.track.sponsor.public,model_event_sponsor,,1,0,0,0 access_event_track_stage_all,event.track.stage.all,model_event_track_stage,,1,0,0,0 access_event_track_stage_event_manager,event.track.stage.event.manager,model_event_track_stage,event.group_event_manager,1,1,1,1 +event_track_visitor_access_all,event.track.visitor.access.all,model_event_track_visitor,,0,0,0,0 +event_track_visitor_access_manager,event.track.visitor.access.manager,model_event_track_visitor,event.group_event_manager,1,1,1,1 +event_track_tag_category_access_public,event.track.tag.category.access,model_event_track_tag_category,,1,0,0,0 +event_track_tag_category_access_manager,event.track.tag.category.access.manager,model_event_track_tag_category,event.group_event_manager,1,1,1,1 \ No newline at end of file diff --git a/addons/website_event_track_online/static/lib/idb-keyval/idb-keyval.js b/addons/website_event_track/static/lib/idb-keyval/idb-keyval.js similarity index 100% rename from addons/website_event_track_online/static/lib/idb-keyval/idb-keyval.js rename to addons/website_event_track/static/lib/idb-keyval/idb-keyval.js diff --git a/addons/website_event_track_online/static/src/js/event_track_reminder.js b/addons/website_event_track/static/src/js/event_track_reminder.js similarity index 100% rename from addons/website_event_track_online/static/src/js/event_track_reminder.js rename to addons/website_event_track/static/src/js/event_track_reminder.js diff --git a/addons/website_event_track_online/static/src/js/service_worker.js b/addons/website_event_track/static/src/js/service_worker.js similarity index 98% rename from addons/website_event_track_online/static/src/js/service_worker.js rename to addons/website_event_track/static/src/js/service_worker.js index ac1a8249a15..a994c2123c4 100644 --- a/addons/website_event_track_online/static/src/js/service_worker.js +++ b/addons/website_event_track/static/src/js/service_worker.js @@ -1,4 +1,4 @@ -importScripts("/website_event_track_online/static/lib/idb-keyval/idb-keyval.js"); +importScripts("/website_event_track/static/lib/idb-keyval/idb-keyval.js"); const PREFIX = "odoo-event"; const SYNCABLE_ROUTES = ["/event/track/toggle_reminder"]; diff --git a/addons/website_event_track_online/static/src/js/website_event_pwa_widget.js b/addons/website_event_track/static/src/js/website_event_pwa_widget.js similarity index 97% rename from addons/website_event_track_online/static/src/js/website_event_pwa_widget.js rename to addons/website_event_track/static/src/js/website_event_pwa_widget.js index 77d12e4bb1b..00c2a8ede33 100644 --- a/addons/website_event_track_online/static/src/js/website_event_pwa_widget.js +++ b/addons/website_event_track/static/src/js/website_event_pwa_widget.js @@ -1,4 +1,4 @@ -odoo.define("website_event_track_online.website_event_pwa_widget", function (require) { +odoo.define("website_event_track.website_event_pwa_widget", function (require) { "use strict"; var config = require("web.config"); @@ -6,7 +6,7 @@ odoo.define("website_event_track_online.website_event_pwa_widget", function (req var utils = require("web.utils"); var PWAInstallBanner = publicWidget.Widget.extend({ - xmlDependencies: ["/website_event_track_online/static/src/xml/website_event_pwa.xml"], + xmlDependencies: ["/website_event_track/static/src/xml/website_event_pwa.xml"], template: "pwa_install_banner", events: { "click .o_btn_install": "_onClickInstall", diff --git a/addons/website_event_track/static/src/js/website_event_track_set_customize_options.js b/addons/website_event_track/static/src/js/website_event_track_set_customize_options.js deleted file mode 100644 index c193b16b0c6..00000000000 --- a/addons/website_event_track/static/src/js/website_event_track_set_customize_options.js +++ /dev/null @@ -1,94 +0,0 @@ -odoo.define('website_event_track.set_customize_options', function (require) { -"use strict"; - -var EventSpecificOptions = require('website_event.set_customize_options').EventSpecificOptions; - -EventSpecificOptions.include({ - xmlDependencies: (EventSpecificOptions.prototype.xmlDependencies || []) - .concat([ - '/website_event_track/static/src/xml/customize_options.xml', - ]), - - events: _.extend({}, EventSpecificOptions.prototype.events, { - 'change #display-showcase-talks': '_onDisplayTalksChange', - 'change #allow-talk-proposal': '_onAllowTalkProposalChange', - }), - - start: function () { - this.$displayTalksInput = this.$('#display-showcase-talks'); - this.$allowTalksInput = this.$('#allow-talk-proposal'); - this._super.apply(this, arguments); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - _onAllowTalkProposalChange: function () { - var checkboxValue = this.$allowTalksInput.is(':checked'); - this._toggleTalkProposal(checkboxValue); - }, - - _onDisplayTalksChange: function () { - var checkboxValue = this.$displayTalksInput.is(':checked'); - this._toggleDisplayTalks(checkboxValue); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - _getCheckboxFields: function () { - var fields = this._super(); - fields = _.union(fields, ['website_track', 'website_track_proposal']); - return fields; - }, - - _getCheckboxFieldMatch: function (checkboxField) { - if (checkboxField === 'website_track') { - return this.$displayTalksInput; - } - if (checkboxField === 'website_track_proposal') { - return this.$allowTalksInput; - } - return this._super(checkboxField); - }, - - _initCheckboxCallback: function (rpcData) { - this._super(rpcData); - var submenuInput; - if (rpcData[0]['website_track']) { - submenuInput = this._getCheckboxFieldMatch('website_track'); - submenuInput.attr('checked', 'checked'); - } - if (rpcData[0]['website_track_proposal']) { - submenuInput = this._getCheckboxFieldMatch('website_track_proposal'); - submenuInput.attr('checked', 'checked'); - } - }, - - _toggleTalkProposal: function (val) { - var self = this; - this._rpc({ - model: this.modelName, - method: 'toggle_website_track_proposal', - args: [[this.eventId], val], - }).then(function () { - self._reloadEventPage(); - }); - }, - - _toggleDisplayTalks: function (val) { - var self = this; - this._rpc({ - model: this.modelName, - method: 'toggle_website_track', - args: [[this.eventId], val], - }).then(function () { - self._reloadEventPage(); - }); - } - -}); - -}); diff --git a/addons/website_event_track_online/static/src/scss/event_track_templates.scss b/addons/website_event_track/static/src/scss/event_track_templates.scss similarity index 100% rename from addons/website_event_track_online/static/src/scss/event_track_templates.scss rename to addons/website_event_track/static/src/scss/event_track_templates.scss diff --git a/addons/website_event_track_online/static/src/scss/pwa_frontend.scss b/addons/website_event_track/static/src/scss/pwa_frontend.scss similarity index 100% rename from addons/website_event_track_online/static/src/scss/pwa_frontend.scss rename to addons/website_event_track/static/src/scss/pwa_frontend.scss diff --git a/addons/website_event_track/static/src/xml/customize_options.xml b/addons/website_event_track/static/src/xml/customize_options.xml deleted file mode 100644 index b7d64bca0db..00000000000 --- a/addons/website_event_track/static/src/xml/customize_options.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/addons/website_event_track_online/static/src/xml/website_event_pwa.xml b/addons/website_event_track/static/src/xml/website_event_pwa.xml similarity index 100% rename from addons/website_event_track_online/static/src/xml/website_event_pwa.xml rename to addons/website_event_track/static/src/xml/website_event_pwa.xml diff --git a/addons/website_event_track/tests/__init__.py b/addons/website_event_track/tests/__init__.py index 3773e715842..12b9da864c8 100644 --- a/addons/website_event_track/tests/__init__.py +++ b/addons/website_event_track/tests/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from . import common from . import test_website_event diff --git a/addons/website_event_track_online/tests/common.py b/addons/website_event_track/tests/common.py similarity index 100% rename from addons/website_event_track_online/tests/common.py rename to addons/website_event_track/tests/common.py diff --git a/addons/website_event_track/views/assets.xml b/addons/website_event_track/views/assets.xml index d580f4e0106..120f828839d 100644 --- a/addons/website_event_track/views/assets.xml +++ b/addons/website_event_track/views/assets.xml @@ -2,10 +2,16 @@