[MERGE][REM] website_event_(track)(_online/_session): merge with their base modules
PURPOSE Clean organization and models linked to Event Online feature introduced in semi stable saas-13.3 at odoo/odoo@981f95bbf4 and odoo/enterprise@14722028ae Also clean website event menu not being available in website event but only in website event track, which implies some extra-code to manage it. In short: merge website_event_online in website_event, website_event_track _(online/session) in website_event_track. RATIONALE _online modules have been added to extend content of website_event and website_event_track without having any impact on those module. First step of cleaning is to move this content directly in base module. track_session module is mainly a rewrite of track module. Second step of cleaning is to move its content directly in website_event_track. SPECIFICATIONS Move website.event.menu model from website event track to website event module. It allows to define community menu in website_event and re-use it in website_event_meet and website_event_track_quiz, removing some unnecessary dependencies. Move event.tag color field from website event online to event. This field is part of standard event models. Move remaining content of website event online to website event. 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. 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. Ease discovering of online features: set bridges as auto install and allow to configure CTA button on event from event_type, like other frontend related menus and buttons, and add settings to install meet, exhibitors, live mode and quizzed. Update dependencies accordingly. LINKS Task ID-2319779 COM odoo/odoo#56067 ENT odoo/enterprise#12520 UPG odoo/upgrade#1654 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': 'Events Organization',
|
||||
'version': '1.2',
|
||||
'version': '1.3',
|
||||
'website': 'https://www.odoo.com/page/events',
|
||||
'category': 'Marketing/Events',
|
||||
'summary': 'Trainings, Conferences, Meetings, Exhibitions, Registrations',
|
||||
|
||||
@@ -42,51 +42,60 @@
|
||||
<field name="name">5-10</field>
|
||||
<field name="sequence">1</field>
|
||||
<field name="category_id" ref="event_tag_category_1"/>
|
||||
<field name="color">1</field>
|
||||
</record>
|
||||
|
||||
<record id="event_tag_category_1_tag_2" model="event.tag">
|
||||
<field name="name">10-14</field>
|
||||
<field name="sequence">2</field>
|
||||
<field name="category_id" ref="event_tag_category_1"/>
|
||||
<field name="color">2</field>
|
||||
</record>
|
||||
|
||||
<record id="event_tag_category_1_tag_3" model="event.tag">
|
||||
<field name="name">15-18</field>
|
||||
<field name="sequence">3</field>
|
||||
<field name="category_id" ref="event_tag_category_1"/>
|
||||
<field name="color">3</field>
|
||||
</record>
|
||||
|
||||
<record id="event_tag_category_1_tag_4" model="event.tag">
|
||||
<field name="name">18+</field>
|
||||
<field name="sequence">4</field>
|
||||
<field name="category_id" ref="event_tag_category_1"/>
|
||||
<field name="color">4</field>
|
||||
</record>
|
||||
|
||||
<record id="event_tag_category_2_tag_1" model="event.tag">
|
||||
<field name="name">Culture</field>
|
||||
<field name="sequence">10</field>
|
||||
<field name="category_id" ref="event_tag_category_2"/>
|
||||
<field name="color">5</field>
|
||||
</record>
|
||||
<record id="event_tag_category_2_tag_2" model="event.tag">
|
||||
<field name="name">Music</field>
|
||||
<field name="sequence">11</field>
|
||||
<field name="category_id" ref="event_tag_category_2"/>
|
||||
<field name="color">6</field>
|
||||
</record>
|
||||
<record id="event_tag_category_2_tag_3" model="event.tag">
|
||||
<field name="name">Sport</field>
|
||||
<field name="sequence">12</field>
|
||||
<field name="category_id" ref="event_tag_category_2"/>
|
||||
<field name="color">7</field>
|
||||
</record>
|
||||
|
||||
<record id="event_tag_category_3_tag_1" model="event.tag">
|
||||
<field name="name">Online</field>
|
||||
<field name="sequence">20</field>
|
||||
<field name="category_id" ref="event_tag_category_3"/>
|
||||
<field name="color">8</field>
|
||||
</record>
|
||||
<record id="event_tag_category_3_tag_2" model="event.tag">
|
||||
<field name="name">Conference</field>
|
||||
<field name="sequence">21</field>
|
||||
<field name="category_id" ref="event_tag_category_3"/>
|
||||
<field name="color">9</field>
|
||||
</record>
|
||||
|
||||
</data></odoo>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from random import randint
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class EventTagCategory(models.Model):
|
||||
@@ -18,9 +20,15 @@ class EventTag(models.Model):
|
||||
_description = "Event Tag"
|
||||
_order = "sequence"
|
||||
|
||||
def _default_color(self):
|
||||
return randint(1, 11)
|
||||
|
||||
name = fields.Char("Name", required=True, translate=True)
|
||||
sequence = fields.Integer('Sequence', default=0)
|
||||
category_id = fields.Many2one("event.tag.category", string="Category", required=True, ondelete='cascade')
|
||||
color = fields.Integer(
|
||||
string='Color Index', default=lambda self: self._default_color(),
|
||||
help='Tag color. No color means no display in kanban or front-end, to distinguish internal tags from public categorization tags.')
|
||||
|
||||
def name_get(self):
|
||||
return [(tag.id, "%s: %s" % (tag.category_id.name, tag.name)) for tag in self]
|
||||
|
||||
@@ -6,7 +6,11 @@ class ResConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
module_event_sale = fields.Boolean("Tickets")
|
||||
module_website_event_meet = fields.Boolean("Discussion Rooms")
|
||||
module_website_event_track = fields.Boolean("Tracks and Agenda")
|
||||
module_website_event_track_live = fields.Boolean("Live Mode")
|
||||
module_website_event_track_quiz = fields.Boolean("Quiz on Tracks")
|
||||
module_website_event_track_exhibitor = fields.Boolean("Advanced Sponsors")
|
||||
module_website_event_questions = fields.Boolean("Registration Survey")
|
||||
module_event_barcode = fields.Boolean("Barcode")
|
||||
module_website_event_sale = fields.Boolean("Online Ticketing")
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<tree string="Event Category">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="tag_ids" widget="many2many_tags" />
|
||||
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -28,6 +28,7 @@
|
||||
<tree string="Tags" editable="bottom">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="color" widget="color_picker"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
@@ -51,6 +52,7 @@
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="category_id"/>
|
||||
<field name="color" widget="color_picker"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -64,6 +66,7 @@
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="category_id" widget="many2one"/>
|
||||
<field name="color" widget="color_picker"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
<label for="tag_ids" string="Tags"/>
|
||||
<div class="row">
|
||||
<div class="col-12 mt16">
|
||||
<field name="tag_ids" widget="many2many_tags" options="{'no_quick_create': True}"/>
|
||||
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color', 'no_quick_create': True}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -232,7 +232,7 @@
|
||||
</div>
|
||||
<field name="date_tz"/>
|
||||
<field name="event_type_id" string="Template" options="{'no_create':True}"/>
|
||||
<field name="tag_ids" widget="many2many_tags" options="{'no_quick_create': True}"/>
|
||||
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color', 'no_quick_create': True}"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="organizer_id"/>
|
||||
|
||||
@@ -22,6 +22,44 @@
|
||||
<div class="text-muted">
|
||||
Manage & publish a schedule with tracks
|
||||
</div>
|
||||
<div class="mt-3 d-flex" attrs="{'invisible': [('module_website_event_track', '=', False)]}">
|
||||
<field name="module_website_event_track_live" class="w-auto"/>
|
||||
<div>
|
||||
<label string="Live Mode" for="module_website_event_track_live"/><br/>
|
||||
<span class="text-muted">Youtube support, Chat</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 d-flex" attrs="{'invisible': [('module_website_event_track', '=', False)]}">
|
||||
<field name="module_website_event_track_quiz" class="w-auto"/>
|
||||
<div>
|
||||
<label string="Quizzes on tracks" for="module_website_event_track_quiz"/><br/>
|
||||
<span class="text-muted">Quizzes, leaderboard</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 o_setting_box">
|
||||
<div>
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="module_website_event_meet"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label string="Discussion Rooms" for="module_website_event_meet"/>
|
||||
<div class="text-muted">
|
||||
Integrate discussion rooms
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="module_website_event_track_exhibitor"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label string="Advanced Sponsors" for="module_website_event_track_exhibitor"/>
|
||||
<div class="text-muted">
|
||||
Chat, advanced exhibitors
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -16,14 +16,11 @@ automatic lead generation, full Online support, ...
|
||||
'event_sale',
|
||||
'website_event_crm_questions',
|
||||
'website_event_questions',
|
||||
'website_event_online',
|
||||
'website_event_questions',
|
||||
'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',
|
||||
'website_event_track_quiz',
|
||||
],
|
||||
|
||||
@@ -7,7 +7,7 @@ from odoo.addons.base.tests.common import HttpCaseWithUserDemo, HttpCaseWithUser
|
||||
from odoo.addons.event_crm.tests.common import TestEventCrmCommon
|
||||
from odoo.addons.sales_team.tests.common import TestSalesCommon
|
||||
from odoo.addons.website.tests.test_website_visitor import MockVisitor
|
||||
from odoo.addons.website_event_online.tests.common import EventDtPatcher
|
||||
from odoo.addons.website_event.tests.common import EventDtPatcher
|
||||
|
||||
|
||||
class TestEventFullCommon(TestEventCrmCommon, TestSalesCommon, EventDtPatcher, MockVisitor):
|
||||
|
||||
@@ -1,29 +1,45 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Events',
|
||||
'version': '1.1',
|
||||
'category': 'Marketing/Events',
|
||||
'sequence': 140,
|
||||
'summary': 'Publish events, sell tickets',
|
||||
'website': 'https://www.odoo.com/page/events',
|
||||
'description': "",
|
||||
'depends': ['website', 'website_partner', 'website_mail', 'event'],
|
||||
'depends': [
|
||||
'event',
|
||||
'website',
|
||||
'website_partner',
|
||||
'website_mail',
|
||||
],
|
||||
'data': [
|
||||
'data/event_data.xml',
|
||||
'views/assets.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/event_snippets.xml',
|
||||
'views/event_templates.xml',
|
||||
'views/event_templates_misc_pages.xml',
|
||||
'views/event_templates_registration.xml',
|
||||
'views/event_templates_list.xml',
|
||||
'views/event_templates_page.xml',
|
||||
'views/event_templates_page_registration.xml',
|
||||
'views/event_templates_page_misc.xml',
|
||||
'views/event_templates_widgets.xml',
|
||||
'views/website_templates.xml',
|
||||
'views/event_views.xml',
|
||||
'views/event_event_views.xml',
|
||||
'views/event_registration_views.xml',
|
||||
'views/event_type_views.xml',
|
||||
'views/website_event_menu_views.xml',
|
||||
'views/website_visitor_views.xml',
|
||||
'views/event_menus.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'security/event_security.xml',
|
||||
],
|
||||
'demo': [
|
||||
'data/res_partner_demo.xml',
|
||||
'data/event_demo.xml'
|
||||
'data/website_visitor_demo.xml',
|
||||
'data/event_demo.xml',
|
||||
'data/event_registration_demo.xml',
|
||||
],
|
||||
'application': True,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import main
|
||||
from . import community
|
||||
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.website_event.controllers.main import WebsiteEventController
|
||||
from odoo import fields, http, modules, tools
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
|
||||
class WebsiteEventCommunityController(http.Controller):
|
||||
|
||||
class EventCommunityController(http.Controller):
|
||||
|
||||
@http.route('/event/<model("event.event"):event>/community', type="http", auth="public", website=True)
|
||||
def community(self, event, lang=None, **kwargs):
|
||||
@@ -345,13 +345,34 @@ class WebsiteEventController(http.Controller):
|
||||
return list(registrations.values())
|
||||
|
||||
def _create_attendees_from_registration_post(self, event, registration_data):
|
||||
""" Also try to set a visitor (from request) and
|
||||
a partner (if visitor linked to a user for example). Purpose is to gather
|
||||
as much informations as possible, notably to ease future communications.
|
||||
Also try to update visitor informations based on registration info. """
|
||||
visitor_sudo = request.env['website.visitor']._get_visitor_from_request(force_create=True)
|
||||
visitor_sudo._update_visitor_last_visit()
|
||||
visitor_values = {}
|
||||
|
||||
registrations_to_create = []
|
||||
for registration_values in registration_data:
|
||||
registration_values['event_id'] = event.id
|
||||
if not registration_values.get('partner_id'):
|
||||
if not registration_values.get('partner_id') and visitor_sudo.partner_id:
|
||||
registration_values['partner_id'] = visitor_sudo.partner_id.id
|
||||
elif not registration_values.get('partner_id'):
|
||||
registration_values['partner_id'] = request.env.user.partner_id.id
|
||||
|
||||
if visitor_sudo:
|
||||
# registration may give a name to the visitor, yay
|
||||
if registration_values.get('name') and not visitor_sudo.name and not visitor_values.get('name'):
|
||||
visitor_values['name'] = registration_values['name']
|
||||
# update registration based on visitor
|
||||
registration_values['visitor_id'] = visitor_sudo.id
|
||||
|
||||
registrations_to_create.append(registration_values)
|
||||
|
||||
if visitor_values:
|
||||
visitor_sudo.write(visitor_values)
|
||||
|
||||
return request.env['event.registration'].sudo().create(registrations_to_create)
|
||||
|
||||
@http.route(['''/event/<model("event.event"):event>/registration/confirm'''], type='http', auth="public", methods=['POST'], website=True)
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
<record id="event.event_1" model="event.event">
|
||||
<field name="website_menu" eval="True"/>
|
||||
<field name="website_published" eval="True"/>
|
||||
<field name="menu_register_cta" eval="True"/>
|
||||
<field name="subtitle">The Great Reno Balloon Race is the world's largest free hot-air ballooning event.</field>
|
||||
<field name="cover_properties">{"background-image": "url('/website_event/static/src/img/event_cover_1.jpg')", "resize_class": "o_record_has_cover o_half_screen_height", "opacity": "0.4"}</field>
|
||||
<field name="description" type="html">
|
||||
@@ -151,6 +152,7 @@
|
||||
<record id="event.event_7" model="event.event">
|
||||
<field name="website_menu" eval="True"/>
|
||||
<field name="website_published" eval="True"/>
|
||||
<field name="menu_register_cta" eval="True"/>
|
||||
<field name="subtitle">Our newest collection will be revealed online! Interact with us on our live streams!</field>
|
||||
<field name="cover_properties">{"background-image": "url('/website_event/static/src/img/event_cover_7.jpg')", "resize_class": "o_record_has_cover o_half_screen_height", "opacity": "0.4"}</field>
|
||||
<field name="description" type="html">
|
||||
|
||||
+4
-4
@@ -3,16 +3,16 @@
|
||||
|
||||
<!-- OpenWood Collection Online Reveal: Gemini (all) -->
|
||||
<record id="event.event_registration_7_0" model="event.registration">
|
||||
<field name="visitor_id" ref="website_event_online.website_visitor_event_0"/>
|
||||
<field name="visitor_id" ref="website_event.website_visitor_event_0"/>
|
||||
</record>
|
||||
<record id="event.event_registration_7_1" model="event.registration">
|
||||
<field name="visitor_id" ref="website_event_online.website_visitor_event_0"/>
|
||||
<field name="visitor_id" ref="website_event.website_visitor_event_0"/>
|
||||
</record>
|
||||
<record id="event.event_registration_7_2" model="event.registration">
|
||||
<field name="visitor_id" ref="website_event_online.website_visitor_event_1"/>
|
||||
<field name="visitor_id" ref="website_event.website_visitor_event_1"/>
|
||||
</record>
|
||||
<record id="event.event_registration_7_3" model="event.registration">
|
||||
<field name="visitor_id" ref="website_event_online.website_visitor_event_1"/>
|
||||
<field name="visitor_id" ref="website_event.website_visitor_event_1"/>
|
||||
</record>
|
||||
|
||||
</data></odoo>
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
</record>
|
||||
<record id="website_visitor_event_2_1" model="website.visitor">
|
||||
<field name="name">Philipe J. Fry (old)</field>
|
||||
<field name="parent_id" ref="website_event_online.website_visitor_event_2"/>
|
||||
<field name="parent_id" ref="website_event.website_visitor_event_2"/>
|
||||
<field name="partner_id" eval="False"/>
|
||||
<field name="country_id" ref="base.be"/>
|
||||
</record>
|
||||
@@ -1,4 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import event_event
|
||||
from . import event_registration
|
||||
from . import event_type
|
||||
from . import website
|
||||
from . import website_event_menu
|
||||
from . import website_menu
|
||||
from . import website_visitor
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import werkzeug.urls
|
||||
import json
|
||||
import werkzeug.urls
|
||||
|
||||
from pytz import utc
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.addons.http_routing.models.ir_http import slug
|
||||
from odoo.osv import expression
|
||||
|
||||
GOOGLE_CALENDAR_URL = 'https://www.google.com/calendar/render?'
|
||||
|
||||
|
||||
class EventType(models.Model):
|
||||
_name = 'event.type'
|
||||
_inherit = ['event.type']
|
||||
|
||||
website_menu = fields.Boolean('Display a dedicated menu on Website')
|
||||
|
||||
|
||||
class Event(models.Model):
|
||||
_name = 'event.event'
|
||||
_inherit = ['event.event', 'website.seo.metadata', 'website.published.multi.mixin', 'website.cover_properties.mixin']
|
||||
_inherit = [
|
||||
'event.event',
|
||||
'website.seo.metadata',
|
||||
'website.published.multi.mixin',
|
||||
'website.cover_properties.mixin'
|
||||
]
|
||||
|
||||
def _default_cover_properties(self):
|
||||
res = super()._default_cover_properties()
|
||||
@@ -38,23 +39,68 @@ class Event(models.Model):
|
||||
help="Creates menus Introduction, Location and Register on the page "
|
||||
"of the event on the website.")
|
||||
menu_id = fields.Many2one('website.menu', 'Event Menu', copy=False)
|
||||
menu_register_cta = fields.Boolean(
|
||||
'Add Register Button', compute='_compute_menu_register_cta',
|
||||
readonly=False, store=True)
|
||||
community_menu = fields.Boolean(
|
||||
"Community Menu", compute="_compute_community_menu",
|
||||
readonly=False, store=True,
|
||||
help="Display community tab on website")
|
||||
community_menu_ids = fields.One2many(
|
||||
"website.event.menu", "event_id", string="Event Community Menus",
|
||||
domain=[("menu_type", "=", "community")])
|
||||
# live information
|
||||
is_ongoing = fields.Boolean(
|
||||
'Is Ongoing', compute='_compute_time_data', search='_search_is_ongoing',
|
||||
help="Whether event has begun")
|
||||
is_done = fields.Boolean(
|
||||
'Is Done', compute='_compute_time_data',
|
||||
help="Whether event is finished")
|
||||
start_today = fields.Boolean(
|
||||
'Start Today', compute='_compute_time_data',
|
||||
help="Whether event is going to start today if still not ongoing")
|
||||
start_remaining = fields.Integer(
|
||||
'Remaining before start', compute='_compute_time_data',
|
||||
help="Remaining time before event starts (minutes)")
|
||||
|
||||
def _compute_is_participating(self):
|
||||
# we don't allow public user to see participating label
|
||||
if self.env.user != self.env['website'].get_current_website().user_id:
|
||||
email = self.env.user.partner_id.email
|
||||
for event in self:
|
||||
domain = ['&', '|', ('email', '=', email), ('partner_id', '=', self.env.user.partner_id.id), ('event_id', '=', event.id)]
|
||||
event.is_participating = self.env['event.registration'].sudo().search_count(domain)
|
||||
else:
|
||||
self.is_participating = False
|
||||
"""Heuristic
|
||||
|
||||
* public, no visitor: not participating as we have no information;
|
||||
* public and visitor: check visitor is linked to a registration. As
|
||||
visitors are merged on the top parent, current visitor check is
|
||||
sufficient even for successive visits;
|
||||
* logged, no visitor: check partner is linked to a registration. Do
|
||||
not check the email as it is not really secure;
|
||||
* logged as visitor: check partner or visitor are linked to a
|
||||
registration;
|
||||
"""
|
||||
current_visitor = self.env['website.visitor']._get_visitor_from_request(force_create=False)
|
||||
if self.env.user._is_public() and not current_visitor:
|
||||
events = self.env['event.event']
|
||||
elif self.env.user._is_public():
|
||||
events = self.env['event.registration'].sudo().search([
|
||||
('event_id', 'in', self.ids),
|
||||
('visitor_id', '=', current_visitor.id),
|
||||
]).event_id
|
||||
else:
|
||||
if 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)]
|
||||
events = self.env['event.registration'].sudo().search(
|
||||
expression.AND([
|
||||
domain,
|
||||
[('event_id', 'in', self.ids)]
|
||||
])
|
||||
).event_id
|
||||
|
||||
@api.depends('name')
|
||||
def _compute_website_url(self):
|
||||
super(Event, self)._compute_website_url()
|
||||
for event in self:
|
||||
if event.id: # avoid to perform a slug on a not yet saved record in case of an onchange.
|
||||
event.website_url = '/event/%s' % slug(event)
|
||||
event.is_participating = event in events
|
||||
|
||||
@api.depends('event_type_id')
|
||||
def _compute_website_menu(self):
|
||||
@@ -66,6 +112,56 @@ class Event(models.Model):
|
||||
elif not event.website_menu:
|
||||
event.website_menu = False
|
||||
|
||||
@api.depends("event_type_id", "website_menu", "community_menu")
|
||||
def _compute_community_menu(self):
|
||||
""" At type onchange: synchronize. At website_menu update: synchronize. """
|
||||
for event in self:
|
||||
if event.event_type_id and event.event_type_id != event._origin.event_type_id:
|
||||
event.community_menu = event.event_type_id.community_menu
|
||||
elif event.website_menu and event.website_menu != event._origin.website_menu or not event.community_menu:
|
||||
event.community_menu = True
|
||||
elif not event.website_menu:
|
||||
event.community_menu = False
|
||||
|
||||
@api.depends("event_type_id", "website_menu")
|
||||
def _compute_menu_register_cta(self):
|
||||
""" At type onchange: synchronize. At website_menu update: synchronize. """
|
||||
for event in self:
|
||||
if event.event_type_id and event.event_type_id != event._origin.event_type_id:
|
||||
event.menu_register_cta = event.event_type_id.menu_register_cta
|
||||
elif event.website_menu and event.website_menu != event._origin.website_menu or not event.menu_register_cta:
|
||||
event.menu_register_cta = True
|
||||
elif not event.website_menu:
|
||||
event.menu_register_cta = False
|
||||
|
||||
@api.depends('date_begin', 'date_end')
|
||||
def _compute_time_data(self):
|
||||
""" Compute start and remaining time. Do everything in UTC as we compute only
|
||||
time deltas here. """
|
||||
now_utc = utc.localize(fields.Datetime.now().replace(microsecond=0))
|
||||
for event in self:
|
||||
date_begin_utc = utc.localize(event.date_begin, is_dst=False)
|
||||
date_end_utc = utc.localize(event.date_end, is_dst=False)
|
||||
event.is_ongoing = date_begin_utc <= now_utc <= date_end_utc
|
||||
event.is_done = now_utc > date_end_utc
|
||||
event.start_today = date_begin_utc.date() == now_utc.date()
|
||||
if date_begin_utc >= now_utc:
|
||||
td = date_begin_utc - now_utc
|
||||
event.start_remaining = int(td.total_seconds() / 60)
|
||||
else:
|
||||
event.start_remaining = 0
|
||||
|
||||
@api.depends('name')
|
||||
def _compute_website_url(self):
|
||||
super(Event, self)._compute_website_url()
|
||||
for event in self:
|
||||
if event.id: # avoid to perform a slug on a not yet saved record in case of an onchange.
|
||||
event.website_url = '/event/%s' % slug(event)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# CRUD
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
res = super(Event, self).create(vals)
|
||||
@@ -93,7 +189,10 @@ class Event(models.Model):
|
||||
|
||||
:return list: list of fields, each of which triggering a menu update
|
||||
like website_menu, website_track, ... """
|
||||
return ['website_menu']
|
||||
return ['website_menu', 'community_menu']
|
||||
|
||||
def _get_menu_type_field_matching(self):
|
||||
return {'community': 'community_menu'}
|
||||
|
||||
def _split_menus_state_by_field(self):
|
||||
""" For each field linked to a menu, get the set of events having this
|
||||
@@ -137,7 +236,7 @@ class Event(models.Model):
|
||||
menus_update_by_field[fname] |= menus_state_by_field[fname]['deactivated'].filtered(lambda event: event[fname])
|
||||
return menus_update_by_field
|
||||
|
||||
def _get_menu_entries(self):
|
||||
def _get_website_menu_entries(self):
|
||||
""" Method returning menu entries to display on the website view of the
|
||||
event, possibly depending on some options in inheriting modules.
|
||||
|
||||
@@ -145,14 +244,22 @@ class Event(models.Model):
|
||||
* name: menu item name
|
||||
* url: if set, url to a route (do not use xml_id in that case);
|
||||
* xml_id: template linked to the page (do not use url in that case);
|
||||
* sequence: specific sequence of menu entry to be set on the menu;
|
||||
* menu_type: type of menu entry (used in inheriting modules to ease
|
||||
menu management; not used in this module in 13.3 due to technical
|
||||
limitations);
|
||||
"""
|
||||
self.ensure_one()
|
||||
return [
|
||||
(_('Introduction'), False, 'website_event.template_intro'),
|
||||
(_('Location'), False, 'website_event.template_location'),
|
||||
(_('Register'), '/event/%s/register' % slug(self), False),
|
||||
(_('Introduction'), False, 'website_event.template_intro', 1, False),
|
||||
(_('Location'), False, 'website_event.template_location', 50, False),
|
||||
(_('Register'), '/event/%s/register' % slug(self), False, 100, False),
|
||||
]
|
||||
|
||||
def _get_community_menu_entries(self):
|
||||
self.ensure_one()
|
||||
return [(_('Community'), '/event/%s/community' % slug(self), False, 80, 'community')]
|
||||
|
||||
def _update_website_menus(self, menus_update_by_field=None):
|
||||
""" Synchronize event configuration and its menu entries for frontend.
|
||||
|
||||
@@ -163,8 +270,44 @@ class Event(models.Model):
|
||||
elif event.website_menu and not event.menu_id:
|
||||
root_menu = self.env['website.menu'].sudo().create({'name': event.name, 'website_id': event.website_id.id})
|
||||
event.menu_id = root_menu
|
||||
for sequence, (name, url, xml_id) in enumerate(event._get_menu_entries()):
|
||||
event._create_menu(sequence, name, url, xml_id)
|
||||
if event.website_menu and (not menus_update_by_field or event in menus_update_by_field.get('website_menu')):
|
||||
for name, url, xml_id, menu_sequence, menu_type in event._get_website_menu_entries():
|
||||
event._create_menu(menu_sequence, name, url, xml_id, menu_type=menu_type)
|
||||
if event.menu_id and (not menus_update_by_field or event in menus_update_by_field.get('community_menu')):
|
||||
event._update_website_menu_entry('community_menu', 'community_menu_ids', '_get_community_menu_entries')
|
||||
|
||||
def _update_website_menu_entry(self, fname_bool, fname_o2m, method_name):
|
||||
""" Generic method to create menu entries based on a flag on event. This
|
||||
method is a bit obscure, but is due to preparation of adding new menus
|
||||
entries and pages for event in a stable version, leading to some constraints
|
||||
while developing.
|
||||
|
||||
:param fname_bool: field name (e.g. website_track)
|
||||
:param fname_o2m: o2m linking towards website.event.menu matching the
|
||||
boolean fields (normally an entry ot website.event.menu with type matching
|
||||
the boolean field name)
|
||||
:param method_name: method returning menu entries information: url, sequence, ...
|
||||
"""
|
||||
self.ensure_one()
|
||||
new_menu = None
|
||||
|
||||
if self[fname_bool] and not self[fname_o2m]:
|
||||
# menus not found but boolean True: get menus to create
|
||||
for sequence, menu_data in enumerate(getattr(self, method_name)()):
|
||||
# some modules have 4 data: name, url, xml_id, menu_type; however we
|
||||
# plan to support sequence in future modules, so this hackish code is
|
||||
# necessary to avoid crashing. Not nice, but stable target = meh.
|
||||
if len(menu_data) == 4:
|
||||
(name, url, xml_id, menu_type) = menu_data
|
||||
menu_sequence = sequence
|
||||
elif len(menu_data) == 5:
|
||||
(name, url, xml_id, menu_sequence, menu_type) = menu_data
|
||||
new_menu = self._create_menu(menu_sequence, name, url, xml_id, menu_type=menu_type)
|
||||
elif not self[fname_bool]:
|
||||
# will cascade delete to the website.event.menu
|
||||
self[fname_o2m].mapped('menu_id').sudo().unlink()
|
||||
|
||||
return new_menu
|
||||
|
||||
def _create_menu(self, sequence, name, url, xml_id, menu_type=False):
|
||||
""" If url: create a website menu. Menu leads directly to the URL that
|
||||
@@ -186,6 +329,12 @@ class Event(models.Model):
|
||||
'sequence': sequence,
|
||||
'website_id': self.website_id.id,
|
||||
})
|
||||
if menu_type:
|
||||
self.env['website.event.menu'].create({
|
||||
'menu_id': website_menu.id,
|
||||
'event_id': self.id,
|
||||
'menu_type': menu_type,
|
||||
})
|
||||
return website_menu
|
||||
|
||||
# ------------------------------------------------------------
|
||||
|
||||
+11
-1
@@ -5,14 +5,24 @@ from odoo import api, fields, models
|
||||
|
||||
|
||||
class EventType(models.Model):
|
||||
_inherit = "event.type"
|
||||
_name = 'event.type'
|
||||
_inherit = ['event.type']
|
||||
|
||||
website_menu = fields.Boolean('Display a dedicated menu on Website')
|
||||
community_menu = fields.Boolean(
|
||||
"Community Menu", compute="_compute_community_menu",
|
||||
readonly=False, store=True,
|
||||
help="Display community tab on website")
|
||||
menu_register_cta = fields.Boolean(
|
||||
'Add Register Button', compute='_compute_menu_register_cta',
|
||||
readonly=False, store=True)
|
||||
|
||||
@api.depends('website_menu')
|
||||
def _compute_community_menu(self):
|
||||
for event_type in self:
|
||||
event_type.community_menu = event_type.website_menu
|
||||
|
||||
@api.depends('website_menu')
|
||||
def _compute_menu_register_cta(self):
|
||||
for event_type in self:
|
||||
event_type.menu_register_cta = event_type.website_menu
|
||||
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class EventMenu(models.Model):
|
||||
_name = "website.event.menu"
|
||||
_description = "Website Event Menu"
|
||||
|
||||
menu_id = fields.Many2one('website.menu', string='Menu', ondelete='cascade')
|
||||
event_id = fields.Many2one('event.event', string='Event', ondelete='cascade')
|
||||
menu_type = fields.Selection([
|
||||
('community', 'Community Menu')], string="Menu Type",
|
||||
ondelete={'community': 'cascade'}, required=True)
|
||||
@@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class WebsiteMenu(models.Model):
|
||||
_inherit = "website.menu"
|
||||
|
||||
def unlink(self):
|
||||
""" Override to synchronize event configuration fields with menu deletion.
|
||||
This should be cleaned in upcoming versions. """
|
||||
event_updates = {}
|
||||
website_event_menus = self.env['website.event.menu'].search([('menu_id', 'in', self.ids)])
|
||||
for event_menu in website_event_menus:
|
||||
to_update = event_updates.setdefault(event_menu.event_id, list())
|
||||
for menu_type, fname in event_menu.event_id._get_menu_type_field_matching().items():
|
||||
if event_menu.menu_type == menu_type:
|
||||
to_update.append(fname)
|
||||
|
||||
# call super that resumes the unlink of menus entries (including website event menus)
|
||||
res = super(WebsiteMenu, self).unlink()
|
||||
|
||||
# update events
|
||||
for event, to_update in event_updates.items():
|
||||
if to_update:
|
||||
event.write(dict((fname, False) for fname in to_update))
|
||||
|
||||
return res
|
||||
@@ -12,6 +12,17 @@
|
||||
<field name="perm_unlink" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record id="ir_rule_event_tag_public" model="ir.rule">
|
||||
<field name="name">Event Tag: public/portal: color = published</field>
|
||||
<field name="model_id" ref="event.model_event_tag"/>
|
||||
<field name="domain_force">['&', ('color', '!=', False), ('color', '!=', 0)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_public')), (4, ref('base.group_portal'))]"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_unlink" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record id="ir_rule_event_event_ticket_public" model="ir.rule">
|
||||
<field name="name">Event Ticket: public/portal: published read</field>
|
||||
<field name="model_id" ref="event.model_event_event_ticket"/>
|
||||
|
||||
@@ -6,3 +6,6 @@ access_event_event_ticket_public,event.event.ticket.public,event.model_event_eve
|
||||
access_event_event_ticket_portal,event.event.ticket.portal,event.model_event_event_ticket,base.group_portal,1,0,0,0
|
||||
access_event_category_public,event.tag.category.public,event.model_event_tag_category,,1,0,0,0
|
||||
access_event_tag_public,event.tag.public,event.model_event_tag,,1,0,0,0
|
||||
access_website_event_menu_public,website.event.menu,model_website_event_menu,,1,0,0,0
|
||||
access_website_event_menu_manager,website.event.menu,model_website_event_menu,event.group_event_manager,1,1,1,1
|
||||
website_visitor_access_event_user,website.visitor.access.event.user,model_website_visitor,event.group_event_user,1,1,0,0
|
||||
|
||||
|
+1
-1
@@ -1,4 +1,4 @@
|
||||
odoo.define('website_event_online.display_timer_widget', function (require) {
|
||||
odoo.define('website_event.display_timer_widget', function (require) {
|
||||
'use strict';
|
||||
|
||||
var core = require('web.core');
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
odoo.define('website_event_online.register_toaster_widget', function (require) {
|
||||
odoo.define('website_event.register_toaster_widget', function (require) {
|
||||
'use strict';
|
||||
|
||||
let core = require('web.core');
|
||||
@@ -4,5 +4,6 @@
|
||||
from . import common
|
||||
from . import test_event_internals
|
||||
from . import test_ui
|
||||
from . import test_visitor_events
|
||||
from . import test_website_event
|
||||
from . import test_website_event_access
|
||||
|
||||
@@ -1,8 +1,59 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime, timedelta, time
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.addons.event.tests.common import TestEventCommon
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.fields import Datetime as FieldsDatetime, Date as FieldsDate
|
||||
from odoo.tests.common import SavepointCase
|
||||
|
||||
|
||||
class EventDtPatcher(SavepointCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(EventDtPatcher, cls).setUpClass()
|
||||
|
||||
cls.reference_now = datetime(2020, 7, 6, 10, 0, 0)
|
||||
cls.reference_today = datetime(2020, 7, 6)
|
||||
|
||||
cls.event_dt = patch(
|
||||
'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
|
||||
)
|
||||
cls.event_date = patch(
|
||||
'odoo.addons.event.models.event_event.fields.Date',
|
||||
wraps=FieldsDate
|
||||
)
|
||||
cls.wevent_main_date = patch(
|
||||
'odoo.addons.website_event.controllers.main.fields.Date',
|
||||
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)
|
||||
|
||||
|
||||
class TestWebsiteEventCommon(TestEventCommon):
|
||||
@@ -43,3 +94,16 @@ class TestWebsiteEventCommon(TestEventCommon):
|
||||
# without website.event.menu model (or crappy code based on name)
|
||||
# else:
|
||||
# self.assertFalse(bool(view))
|
||||
|
||||
|
||||
class TestEventOnlineCommon(TestEventCommon, EventDtPatcher):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestEventOnlineCommon, cls).setUpClass()
|
||||
|
||||
# event if 8-18 in Europe/Brussels (DST) (first day: begins at 9, last day: ends at 15)
|
||||
cls.event_0.write({
|
||||
'date_begin': datetime.combine(cls.reference_now, time(7, 0)) - timedelta(days=1),
|
||||
'date_end': datetime.combine(cls.reference_now, time(13, 0)) + timedelta(days=1),
|
||||
})
|
||||
|
||||
@@ -17,6 +17,7 @@ class TestEventWebsite(TestWebsiteEventCommon):
|
||||
'date_begin': fields.Datetime.to_string(datetime.today() + timedelta(days=1)),
|
||||
'date_end': fields.Datetime.to_string(datetime.today() + timedelta(days=15)),
|
||||
'website_menu': True,
|
||||
'community_menu': False,
|
||||
})
|
||||
self._assert_website_menus(event)
|
||||
|
||||
@@ -27,6 +28,7 @@ class TestEventWebsite(TestWebsiteEventCommon):
|
||||
'date_begin': fields.Datetime.to_string(datetime.today() + timedelta(days=1)),
|
||||
'date_end': fields.Datetime.to_string(datetime.today() + timedelta(days=15)),
|
||||
'website_menu': True,
|
||||
'community_menu': False,
|
||||
})
|
||||
self.assertTrue(event.website_menu)
|
||||
self._assert_website_menus(event)
|
||||
|
||||
@@ -10,8 +10,11 @@
|
||||
<template id="assets_frontend" inherit_id="website.assets_frontend" name="Event Assets Frontend">
|
||||
<xpath expr="//link[last()]" position="after">
|
||||
<link rel="stylesheet" type="text/scss" href="/website_event/static/src/scss/website_event.scss"/>
|
||||
<link rel="stylesheet" type="text/scss" href="/website_event/static/src/scss/website_event_templates.scss"/>
|
||||
</xpath>
|
||||
<xpath expr="//script[last()]" position="after">
|
||||
<script type="text/javascript" src="/website_event/static/src/js/display_timer_widget.js"></script>
|
||||
<script type="text/javascript" src="/website_event/static/src/js/register_toaster_widget.js"></script>
|
||||
<script type="text/javascript" src="/website_event/static/src/js/website_geolocation.js"></script>
|
||||
<script type="text/javascript" src="/website_event/static/src/js/website_event.js"></script>
|
||||
<script type="text/javascript" src="/website_event/static/src/js/website_event_ticket_details.js"></script>
|
||||
|
||||
+12
-26
@@ -1,31 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="event_type_view_form_inherit_website" model="ir.ui.view">
|
||||
<field name="name">event.type.view.form.inherit.website</field>
|
||||
<field name="model">event.type</field>
|
||||
<field name="inherit_id" ref="event.view_event_type_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<div name="event_type_visibility_seats" position="after">
|
||||
<div class="col-12 col-lg-6 o_setting_box" name="event_type_visibility_website">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="website_menu"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="website_menu"/>
|
||||
<div class="row" name="website_menu">
|
||||
<div class="col-lg-8 mt16 text-muted">
|
||||
Check this option to have menus for your event on the
|
||||
website: registrations, schedule, map, ...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="event_event_view_form_inherit_website" model="ir.ui.view">
|
||||
<record id="event_event_view_form" model="ir.ui.view">
|
||||
<field name="name">event.event.view.form.inherit.website</field>
|
||||
<field name="model">event.event</field>
|
||||
<field name="priority" eval="5"/>
|
||||
@@ -43,10 +19,20 @@
|
||||
type="object"
|
||||
string="Preview Badges"/>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('oe_title')]" position="after">
|
||||
<div name="event_menu_configuration" groups="base.group_no_one">
|
||||
<label for="website_menu" string="Website Submenu"/>
|
||||
<field name="website_menu"/>
|
||||
<label for="menu_register_cta" string="Register Button"/>
|
||||
<field name="menu_register_cta"/>
|
||||
<label for="community_menu" string="Community"/>
|
||||
<field name="community_menu"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="event_event_view_list_inherit_website" model="ir.ui.view">
|
||||
<record id="event_event_view_list" model="ir.ui.view">
|
||||
<field name="name">event.event.view.list.inherit.website</field>
|
||||
<field name="model">event.event</field>
|
||||
<field name="inherit_id" ref="event.view_event_tree"/>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo><data>
|
||||
|
||||
<menuitem id="menu_website_event_menu"
|
||||
name="Website Menus"
|
||||
action="website_event_menu_action"
|
||||
parent="event.menu_event_configuration"
|
||||
groups="base.group_no_one"
|
||||
sequence="99"/>
|
||||
|
||||
</data></odoo>
|
||||
@@ -12,40 +12,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Snippet - Country Events - List -->
|
||||
<template id="country_events_list" name="Country Events List">
|
||||
<div class="country_events_list">
|
||||
<t t-if="events">
|
||||
<h6 class="o_wevent_sidebar_title">
|
||||
<t t-if="country">
|
||||
<i class="fa fa-flag mr-2"/>Events: <span t-esc="country.name"/>
|
||||
<img class="img-fluid" t-att-src="website.image_url(country, 'image')" alt=""/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<i class="fa fa-globe mr-2"/>Upcoming Events
|
||||
</t>
|
||||
</h6>
|
||||
<ul class="list-group mb-3">
|
||||
<li t-foreach="events[:5]" t-as="event_dict" class="list-group-item d-flex justify-content-between">
|
||||
<a t-att-href="event_dict['url']">
|
||||
<i t-if="not event_dict['event'].website_published" class="fa fa-ban text-danger mr-1" role="img" aria-label="Unpublished" title="Unpublished"/>
|
||||
<span t-esc="event_dict['event'].name" t-attf-class="#{(not event_dict['event'].website_published) and 'text-danger' or ''}"/>
|
||||
</a>
|
||||
<span t-esc="event_dict['date']"/>
|
||||
</li>
|
||||
</ul>
|
||||
<div t-if="len(events) > 5">
|
||||
<t t-if="country">
|
||||
<a t-attf-href="/event?country=#{country.id}" class="small"><b>See all events from <span t-esc="country.name"/></b></a>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<a href="/event" class="small"><b>View all</b></a>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Snippets and options -->
|
||||
<template id="snippets" inherit_id="website.snippets">
|
||||
<xpath expr="//t[@id='event_local_events_hook']" position="replace">
|
||||
|
||||
+6
-170
@@ -97,15 +97,16 @@
|
||||
<template id="event_category_tag" inherit_id="website_event.index_topbar" active="False" customize_show="True" name="Filter by Tags">
|
||||
<xpath expr="//ul[hasclass('o_wevent_index_topbar_filters')]" position="inside">
|
||||
<t t-foreach="categories" t-as="category">
|
||||
<li t-if="category.tag_ids" class="nav-item dropdown mr-2 my-1">
|
||||
<li t-if="category.tag_ids and any(tag.color for tag in category.tag_ids)" class="nav-item dropdown mr-2 my-1">
|
||||
<a href="#" role="button" class="btn dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="fa fa-folder-open"/>
|
||||
<t t-esc="category.name"/>
|
||||
</a>
|
||||
<div class="dropdown-menu">
|
||||
<t t-foreach="category.tag_ids" t-as="tag">
|
||||
<a t-att-href="'/event?%s' % keep_query('*', tags=str((search_tags - tag).ids if tag in search_tags else (tag | search_tags).ids))"
|
||||
t-attf-class="dropdown-item d-flex align-items-center justify-content-between #{'active' if tag in search_tags else ''}">
|
||||
<a t-if="tag.color"
|
||||
t-att-href="'/event?%s' % keep_query('*', tags=str((search_tags - tag).ids if tag in search_tags else (tag | search_tags).ids))"
|
||||
t-attf-class="dropdown-item d-flex align-items-center justify-content-between #{'active' if tag in search_tags else ''}">
|
||||
<t t-esc="tag.name"/>
|
||||
</a>
|
||||
</t>
|
||||
@@ -144,23 +145,6 @@
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<!-- Search Box -->
|
||||
<template id="events_search_box" inherit_id="website.website_search_box" primary="True">
|
||||
<xpath expr="//div[@role='search']" position="replace">
|
||||
<form t-attf-class="o_wevent_event_searchbar_form o_wait_lazy_js w-100 my-1 my-lg-0 #{_classes}"
|
||||
t-att-action="action if action else '/event'" method="get">
|
||||
<t t-set="search" t-value="search or _searches and _searches['search']"/>
|
||||
<t t-set="placeholder" t-value="placeholder or _placeholder"/>
|
||||
<t>$0</t>
|
||||
<t t-foreach="_searches" t-as="search">
|
||||
<input t-if="search != 'search' and search_value != 'all'" type="hidden"
|
||||
t-att-name="search" t-att-value="search_value"/>
|
||||
</t>
|
||||
<t t-raw="0"/>
|
||||
</form>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<!-- Index - Events list -->
|
||||
<template id="events_list" name="Events list">
|
||||
<!-- Options -->
|
||||
@@ -222,7 +206,8 @@
|
||||
<div itemprop="location" t-field="event.address_id" t-options="{'widget': 'contact', 'fields': ['city'], 'no_marker': 'true'}"/>
|
||||
<div class="mt8 d-flex align-items-center">
|
||||
<t t-foreach="event.tag_ids" t-as="tag">
|
||||
<span t-attf-class="badge mr8 #{'badge-primary' if tag in search_tags else 'badge-light'}">
|
||||
<span t-if="tag.color"
|
||||
t-attf-class="badge mr8 #{'badge-primary' if tag in search_tags else 'badge-light'} #{'o_tag_color_%s' % tag.color if tag.color else ''}">
|
||||
<span t-esc="tag.name"/>
|
||||
</span>
|
||||
</t>
|
||||
@@ -343,153 +328,4 @@
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<!-- Event -->
|
||||
<template id="layout" name="Event">
|
||||
<t t-call="website.layout">
|
||||
<!-- Options -->
|
||||
<t t-set="opt_events_list_categories" t-value="request.website.viewref('website_event.opt_events_list_categories').active"/>
|
||||
<div id="wrap" class="o_wevent_event js_event">
|
||||
<t t-if="not event.menu_id">
|
||||
<nav class="navbar navbar-light border-top shadow-sm d-print-none">
|
||||
<div class="container align-items-baseline justify-content-start">
|
||||
<a href="/event" class="navbar-brand h4 my-0 mr-0 mr-md-4">
|
||||
<i class="fa fa-long-arrow-left text-primary mr-2"/>
|
||||
<span>All Events</span>
|
||||
</a>
|
||||
<ul class="navbar-nav flex-row ml-md-auto ml-0">
|
||||
<li t-if="opt_events_list_categories" class="nav-item mr-3">
|
||||
<a t-attf-href="/event?type=#{event.event_type_id.id}" t-if="event.event_type_id" class="nav-link">
|
||||
<i class="fa fa-folder-open text-primary mr-2"/><span t-field="event.event_type_id"/>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item mr-3">
|
||||
<a t-if="event.country_id" t-attf-href="/event?country=#{event.country_id.id}" class="nav-link">
|
||||
<i class="fa fa-map-marker text-primary mr-2"/><span t-field="event.country_id"/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="d-flex align-items-centerflex-wrap pl-sm-3 pr-0">
|
||||
<t t-call="website_event.events_search_box">
|
||||
<t t-set="_classes" t-value="'ml-auto'"/>
|
||||
<t t-set="_searches" t-value="searches"/>
|
||||
<t t-set="_placeholder">Search an event ...</t>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<nav class="navbar navbar-light border-top shadow-sm navbar-expand-md">
|
||||
<div class="container align-items-baseline">
|
||||
<a href="#" t-field="event.name" class="navbar-brand h4 my-0 mr-0 mr-md-4"/>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#o_wevent_event_submenu" aria-controls="o_wevent_event_submenu" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div id="o_wevent_event_submenu" class="collapse navbar-collapse">
|
||||
<ul class="navbar-nav w-100" t-att-data-menu_name="editable and 'Event Menu'" t-att-data-content_menu_id="editable and event.menu_id.id">
|
||||
<t t-foreach="event.menu_id.child_id" t-as="submenu">
|
||||
<t t-call="website.submenu">
|
||||
<t t-set="item_class" t-value="'nav-item'"/>
|
||||
<t t-set="link_class" t-value="'nav-link'"/>
|
||||
</t>
|
||||
</t>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</t>
|
||||
<t t-raw="0"/>
|
||||
<t t-set="editor_sub_message">Following content will appear on all events.</t>
|
||||
<div class="oe_structure oe_empty" id="oe_structure_website_event_layout_1" t-att-data-editor-sub-message="editor_sub_message"/>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Event - Description -->
|
||||
<template id="event_description_full" name="Event Description" track="1">
|
||||
<t t-call="website_event.event_details">
|
||||
<section class="bg-200 mt-n5">
|
||||
<div class="container overflow-hidden">
|
||||
<div class="row no-gutters mt-n4 mb-3">
|
||||
<!-- Description -->
|
||||
<div id="o_wevent_event_main_col" class="col-lg-8 bg-white px-3 pt-5 pb-0 shadow-sm">
|
||||
<span t-field="event.description" itemprop="description"/>
|
||||
</div>
|
||||
<div class="col-lg-4 bg-light shadow-sm d-print-none">
|
||||
<!-- Date & Time -->
|
||||
<div class="o_wevent_sidebar_block">
|
||||
<h6 class="o_wevent_sidebar_title">Date & Time</h6>
|
||||
<div class="d-flex">
|
||||
<h5 t-field="event.with_context(tz=event.date_tz).date_begin" class="my-1 mr-1" t-options="{'date_only': 'true', 'format': 'EEEE'}"/>
|
||||
<h5 class="my-1" t-field="event.with_context(tz=event.date_tz).date_begin" t-options="{'date_only': 'true', 'format': 'long'}" itemprop="startDate" t-att-datetime="event.date_begin"/>
|
||||
</div>
|
||||
<t t-if="not event.is_one_day">Start -</t>
|
||||
<span t-field="event.with_context(tz=event.date_tz).date_begin" t-options="{'time_only': 'true', 'format': 'short'}"/>
|
||||
<t t-if="event.is_one_day">
|
||||
<i class="fa fa-long-arrow-right mx-1"/>
|
||||
<span t-field="event.with_context(tz=event.date_tz).date_end" t-options="{'time_only': 'true', 'format': 'short'}"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<i class="fa fa-long-arrow-down d-block text-muted mx-3 my-2" style="font-size: 1.5rem"/>
|
||||
<div class="d-flex">
|
||||
<h5 t-field="event.with_context(tz=event.date_tz).date_end" class="my-1 mr-1" t-options="{'date_only': 'true', 'format': 'EEEE'}"/>
|
||||
<h5 class="my-1" t-field="event.with_context(tz=event.date_tz).date_end" t-options="{'date_only': 'true', 'format': 'long'}"/>
|
||||
</div>
|
||||
<t t-if="not event.is_one_day">End -</t>
|
||||
<span t-field="event.with_context(tz=event.date_tz).date_end" t-options="{'time_only': 'true', 'format': 'short'}"/>
|
||||
</t>
|
||||
<!-- Timezone -->
|
||||
<small t-esc="event.date_tz" class="d-block my-3 text-muted"/>
|
||||
|
||||
<div class="dropdown">
|
||||
<i class="fa fa-calendar mr-1"/>
|
||||
<a href="#" role="button" data-toggle="dropdown">Add to Calendar</a>
|
||||
<div class="dropdown-menu">
|
||||
<a t-att-href="iCal_url" class="dropdown-item">iCal/Outlook</a>
|
||||
<a t-att-href="google_url" class="dropdown-item" target="_blank">Google</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Location -->
|
||||
<div t-if="event.address_id" class="o_wevent_sidebar_block">
|
||||
<h6 class="o_wevent_sidebar_title">Location</h6>
|
||||
<h4 t-field="event.address_id" class="" t-options='{
|
||||
"widget": "contact",
|
||||
"fields": ["name"]
|
||||
}'/>
|
||||
<div itemprop="location" class="mb-2" t-field="event.address_id" t-options='{
|
||||
"widget": "contact",
|
||||
"fields": ["address"],
|
||||
"no_marker": True
|
||||
}'/>
|
||||
<div class="mb-3" t-field="event.address_id" t-options='{
|
||||
"widget": "contact",
|
||||
"fields": ["phone", "mobile", "email"]
|
||||
}'/>
|
||||
<i class="fa fa-map-marker fa-fw" role="img"/>
|
||||
<a t-att-href="event._google_map_link()" target="_blank">Get the direction</a>
|
||||
</div>
|
||||
<!-- Organizer -->
|
||||
<div t-if="event.organizer_id" class="o_wevent_sidebar_block">
|
||||
<h6 class="o_wevent_sidebar_title">Organizer</h6>
|
||||
<h4 t-field="event.organizer_id"/>
|
||||
<div itemprop="location" t-field="event.organizer_id" t-options="{'widget': 'contact', 'fields': ['phone', 'mobile', 'email']}"/>
|
||||
</div>
|
||||
<!-- Social -->
|
||||
<div class="o_wevent_sidebar_block">
|
||||
<h6 class="o_wevent_sidebar_title">SHARE</h6>
|
||||
<p class="mb-2">Find out what people see and say about this event, and join the conversation.</p>
|
||||
<t t-snippet-call="website.s_share">
|
||||
<t t-set="_no_title" t-value="True"/>
|
||||
<t t-set="_classes" t-valuef="o_wevent_sidebar_social mx-n1"/>
|
||||
<t t-set="_link_classes" t-valuef="o_wevent_social_link"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- Event -->
|
||||
<template id="layout" name="Event">
|
||||
<t t-call="website.layout">
|
||||
<!-- Options -->
|
||||
<t t-set="opt_events_list_categories" t-value="request.website.viewref('website_event.opt_events_list_categories').active"/>
|
||||
<div id="wrap" class="o_wevent_event js_event">
|
||||
<t t-if="not event.menu_id">
|
||||
<nav class="navbar navbar-light border-top shadow-sm d-print-none">
|
||||
<div class="container align-items-baseline justify-content-start">
|
||||
<a href="/event" class="navbar-brand h4 my-0 mr-0 mr-md-4">
|
||||
<i class="fa fa-long-arrow-left text-primary mr-2"/>
|
||||
<span>All Events</span>
|
||||
</a>
|
||||
<ul class="navbar-nav flex-row ml-md-auto ml-0">
|
||||
<li t-if="opt_events_list_categories" class="nav-item mr-3">
|
||||
<a t-attf-href="/event?type=#{event.event_type_id.id}" t-if="event.event_type_id" class="nav-link">
|
||||
<i class="fa fa-folder-open text-primary mr-2"/><span t-field="event.event_type_id"/>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item mr-3">
|
||||
<a t-if="event.country_id" t-attf-href="/event?country=#{event.country_id.id}" class="nav-link">
|
||||
<i class="fa fa-map-marker text-primary mr-2"/><span t-field="event.country_id"/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="d-flex align-items-centerflex-wrap pl-sm-3 pr-0">
|
||||
<t t-call="website_event.events_search_box">
|
||||
<t t-set="_classes" t-value="'ml-auto'"/>
|
||||
<t t-set="_searches" t-value="searches"/>
|
||||
<t t-set="_placeholder">Search an event ...</t>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<nav class="navbar navbar-light border-top shadow-sm navbar-expand-md">
|
||||
<div class="container align-items-baseline">
|
||||
<a href="#" t-field="event.name" class="navbar-brand h4 my-0 mr-0 mr-md-4"/>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#o_wevent_event_submenu" aria-controls="o_wevent_event_submenu" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div id="o_wevent_event_submenu" class="collapse navbar-collapse">
|
||||
<ul class="navbar-nav w-100" t-att-data-menu_name="editable and 'Event Menu'" t-att-data-content_menu_id="editable and event.menu_id.id">
|
||||
<t t-foreach="event.menu_id.child_id" t-as="submenu">
|
||||
<t t-call="website.submenu">
|
||||
<t t-set="item_class" t-value="'nav-item'"/>
|
||||
<t t-set="link_class" t-value="'nav-link'"/>
|
||||
</t>
|
||||
</t>
|
||||
</ul>
|
||||
<!-- Add Register additional CTA button, in addition to menus -->
|
||||
<a t-if="event.menu_register_cta and not event.is_participating"
|
||||
t-att-href="'/event/%s/register' % (slug(event))"
|
||||
class="btn btn-primary ml-auto">
|
||||
Register
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</t>
|
||||
<t t-raw="0"/>
|
||||
<t t-set="editor_sub_message">Following content will appear on all events.</t>
|
||||
<div class="oe_structure oe_empty" id="oe_structure_website_event_layout_1" t-att-data-editor-sub-message="editor_sub_message"/>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
+89
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
|
||||
<template id="event_details" name="Event Header">
|
||||
<t t-call="website_event.layout">
|
||||
<div name="event" itemscope="itemscope" itemtype="http://schema.org/Event">
|
||||
@@ -26,6 +27,93 @@
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Event - Description -->
|
||||
<template id="event_description_full" name="Event Description" track="1">
|
||||
<t t-call="website_event.event_details">
|
||||
<section class="bg-200 mt-n5">
|
||||
<div class="container overflow-hidden">
|
||||
<div class="row no-gutters mt-n4 mb-3">
|
||||
<!-- Description -->
|
||||
<div id="o_wevent_event_main_col" class="col-lg-8 bg-white px-3 pt-5 pb-0 shadow-sm">
|
||||
<span t-field="event.description" itemprop="description"/>
|
||||
</div>
|
||||
<div class="col-lg-4 bg-light shadow-sm d-print-none">
|
||||
<!-- Date & Time -->
|
||||
<div class="o_wevent_sidebar_block">
|
||||
<h6 class="o_wevent_sidebar_title">Date & Time</h6>
|
||||
<div class="d-flex">
|
||||
<h5 t-field="event.with_context(tz=event.date_tz).date_begin" class="my-1 mr-1" t-options="{'date_only': 'true', 'format': 'EEEE'}"/>
|
||||
<h5 class="my-1" t-field="event.with_context(tz=event.date_tz).date_begin" t-options="{'date_only': 'true', 'format': 'long'}" itemprop="startDate" t-att-datetime="event.date_begin"/>
|
||||
</div>
|
||||
<t t-if="not event.is_one_day">Start -</t>
|
||||
<span t-field="event.with_context(tz=event.date_tz).date_begin" t-options="{'time_only': 'true', 'format': 'short'}"/>
|
||||
<t t-if="event.is_one_day">
|
||||
<i class="fa fa-long-arrow-right mx-1"/>
|
||||
<span t-field="event.with_context(tz=event.date_tz).date_end" t-options="{'time_only': 'true', 'format': 'short'}"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<i class="fa fa-long-arrow-down d-block text-muted mx-3 my-2" style="font-size: 1.5rem"/>
|
||||
<div class="d-flex">
|
||||
<h5 t-field="event.with_context(tz=event.date_tz).date_end" class="my-1 mr-1" t-options="{'date_only': 'true', 'format': 'EEEE'}"/>
|
||||
<h5 class="my-1" t-field="event.with_context(tz=event.date_tz).date_end" t-options="{'date_only': 'true', 'format': 'long'}"/>
|
||||
</div>
|
||||
<t t-if="not event.is_one_day">End -</t>
|
||||
<span t-field="event.with_context(tz=event.date_tz).date_end" t-options="{'time_only': 'true', 'format': 'short'}"/>
|
||||
</t>
|
||||
<!-- Timezone -->
|
||||
<small t-esc="event.date_tz" class="d-block my-3 text-muted"/>
|
||||
|
||||
<div class="dropdown">
|
||||
<i class="fa fa-calendar mr-1"/>
|
||||
<a href="#" role="button" data-toggle="dropdown">Add to Calendar</a>
|
||||
<div class="dropdown-menu">
|
||||
<a t-att-href="iCal_url" class="dropdown-item">iCal/Outlook</a>
|
||||
<a t-att-href="google_url" class="dropdown-item" target="_blank">Google</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Location -->
|
||||
<div t-if="event.address_id" class="o_wevent_sidebar_block">
|
||||
<h6 class="o_wevent_sidebar_title">Location</h6>
|
||||
<h4 t-field="event.address_id" class="" t-options='{
|
||||
"widget": "contact",
|
||||
"fields": ["name"]
|
||||
}'/>
|
||||
<div itemprop="location" class="mb-2" t-field="event.address_id" t-options='{
|
||||
"widget": "contact",
|
||||
"fields": ["address"],
|
||||
"no_marker": True
|
||||
}'/>
|
||||
<div class="mb-3" t-field="event.address_id" t-options='{
|
||||
"widget": "contact",
|
||||
"fields": ["phone", "mobile", "email"]
|
||||
}'/>
|
||||
<i class="fa fa-map-marker fa-fw" role="img"/>
|
||||
<a t-att-href="event._google_map_link()" target="_blank">Get the direction</a>
|
||||
</div>
|
||||
<!-- Organizer -->
|
||||
<div t-if="event.organizer_id" class="o_wevent_sidebar_block">
|
||||
<h6 class="o_wevent_sidebar_title">Organizer</h6>
|
||||
<h4 t-field="event.organizer_id"/>
|
||||
<div itemprop="location" t-field="event.organizer_id" t-options="{'widget': 'contact', 'fields': ['phone', 'mobile', 'email']}"/>
|
||||
</div>
|
||||
<!-- Social -->
|
||||
<div class="o_wevent_sidebar_block">
|
||||
<h6 class="o_wevent_sidebar_title">SHARE</h6>
|
||||
<p class="mb-2">Find out what people see and say about this event, and join the conversation.</p>
|
||||
<t t-snippet-call="website.s_share">
|
||||
<t t-set="_no_title" t-value="True"/>
|
||||
<t t-set="_classes" t-valuef="o_wevent_sidebar_social mx-n1"/>
|
||||
<t t-set="_link_classes" t-valuef="o_wevent_social_link"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Event - Registration -->
|
||||
<template id="registration_template" name="Registration">
|
||||
<div t-if="request.env.user.has_group('event.group_event_manager')" class="alert alert-info rounded-0 o_website_event_configuration o_not_editable" role="status">
|
||||
@@ -33,6 +121,7 @@
|
||||
<i class="fa fa-pencil mr-2" role="img" aria-label="Edit" title="Edit event registration in backend"/><em>Configure Event Tickets</em>
|
||||
</a>
|
||||
</div>
|
||||
<div t-if="toast_message" class="o_wevent_register_toaster d-none" t-att-data-message="toast_message"/>
|
||||
<div t-if="not event.event_registrations_open" class="bg-white mb-5">
|
||||
<div class="alert alert-info mb-0 d-flex justify-content-between align-items-center" role="status">
|
||||
<t t-if="event.start_sale_date and event.start_sale_date > datetime.date.today()">
|
||||
@@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- Search Box -->
|
||||
<template id="events_search_box" inherit_id="website.website_search_box" primary="True">
|
||||
<xpath expr="//div[@role='search']" position="replace">
|
||||
<form t-attf-class="o_wevent_event_searchbar_form o_wait_lazy_js w-100 my-1 my-lg-0 #{_classes}"
|
||||
t-att-action="action if action else '/event'" method="get">
|
||||
<t t-set="search" t-value="search or _searches and _searches['search']"/>
|
||||
<t t-set="placeholder" t-value="placeholder or _placeholder"/>
|
||||
<t>$0</t>
|
||||
<t t-foreach="_searches" t-as="search">
|
||||
<input t-if="search != 'search' and search_value != 'all'" type="hidden"
|
||||
t-att-name="search" t-att-value="search_value"/>
|
||||
</t>
|
||||
<t t-raw="0"/>
|
||||
</form>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<!-- Snippet - Country Events - List -->
|
||||
<template id="country_events_list" name="Country Events List">
|
||||
<div class="country_events_list">
|
||||
<t t-if="events">
|
||||
<h6 class="o_wevent_sidebar_title">
|
||||
<t t-if="country">
|
||||
<i class="fa fa-flag mr-2"/>Events: <span t-esc="country.name"/>
|
||||
<img class="img-fluid" t-att-src="website.image_url(country, 'image')" alt=""/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<i class="fa fa-globe mr-2"/>Upcoming Events
|
||||
</t>
|
||||
</h6>
|
||||
<ul class="list-group mb-3">
|
||||
<li t-foreach="events[:5]" t-as="event_dict" class="list-group-item d-flex justify-content-between">
|
||||
<a t-att-href="event_dict['url']">
|
||||
<i t-if="not event_dict['event'].website_published" class="fa fa-ban text-danger mr-1" role="img" aria-label="Unpublished" title="Unpublished"/>
|
||||
<span t-esc="event_dict['event'].name" t-attf-class="#{(not event_dict['event'].website_published) and 'text-danger' or ''}"/>
|
||||
</a>
|
||||
<span t-esc="event_dict['date']"/>
|
||||
</li>
|
||||
</ul>
|
||||
<div t-if="len(events) > 5">
|
||||
<t t-if="country">
|
||||
<a t-attf-href="/event?country=#{country.id}" class="small"><b>See all events from <span t-esc="country.name"/></b></a>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<a href="/event" class="small"><b>View all</b></a>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Timer widget -->
|
||||
<template id="display_timer_widget" name="Display Timer Widget">
|
||||
<t t-set="pre_countdown_display" t-value="bool(pre_countdown_text) or pre_countdown_display"/>
|
||||
<t t-set="pre_countdown_time" t-value="datetime.datetime.now().timestamp() + int(pre_remaining_time)"/>
|
||||
|
||||
<div class="o_display_timer"
|
||||
t-att-data-display-class="display_class"
|
||||
t-att-data-main-countdown-time="datetime.datetime.now().timestamp() + int(main_remaining_time)"
|
||||
t-att-data-main-countdown-text="main_countdown_text"
|
||||
t-att-data-main-countdown-display="main_countdown_display"
|
||||
t-att-data-pre-countdown-time="pre_countdown_time"
|
||||
t-att-data-pre-countdown-display="pre_countdown_display"
|
||||
t-att-data-pre-countdown-text="pre_countdown_text">
|
||||
<t t-set="remaining_time" t-value="pre_remaining_time if pre_remaining_time else main_remaining_time"/>
|
||||
<span class="o_display_timer_countdown d-flex justify-content-center">
|
||||
<span class="o_countdown_text pr-1" t-esc="pre_countdown_text if pre_countdown_text else main_countdown_text if not pre_countdown_display else ''"/>
|
||||
<div t-if="int(remaining_time) > 86400"
|
||||
class="o_countdown_metric_container"><span class="o_countdown_remaining o_timer_days pr-1">0</span><span class="o_countdown_metric pr-1">days</span></div>
|
||||
<div t-if="int(remaining_time) > 3600"
|
||||
class="o_countdown_metric_container"><span class="o_countdown_remaining o_timer_hours">00</span><span class="o_countdown_metric">:</span></div>
|
||||
<div class="o_countdown_metric_container"><span class="o_countdown_remaining o_timer_minutes">00</span><span class="o_countdown_metric">:</span></div>
|
||||
<div class="o_countdown_metric_container"><span class="o_countdown_remaining o_timer_seconds">00</span><span class="o_countdown_metric"></span></div>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="event_type_view_form" model="ir.ui.view">
|
||||
<field name="name">event.type.view.form.inherit.website</field>
|
||||
<field name="model">event.type</field>
|
||||
<field name="inherit_id" ref="event.view_event_type_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<div name="event_type_visibility_seats" position="after">
|
||||
<div class="col-12 col-lg-6 o_setting_box" name="event_type_visibility_website">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="website_menu"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="website_menu"/>
|
||||
<div class="row" name="website_menu">
|
||||
<div class="col-lg-8 mt16 text-muted">
|
||||
Check this option to have menus for your event on the
|
||||
website: registrations, schedule, map, ...
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt16" name="menu_register_cta"
|
||||
groups="base.group_no_one"
|
||||
attrs="{'invisible': [('website_menu', '=', False)]}">
|
||||
<label class="col-lg-4" for="menu_register_cta"/> <field name="menu_register_cta"/>
|
||||
</div>
|
||||
<div class="row mt16" name="community_menu"
|
||||
id="community-menu"
|
||||
groups="base.group_no_one"
|
||||
attrs="{'invisible': [('website_menu', '=', False)]}">
|
||||
<label class="col-lg-4" for="community_menu"/> <field name="community_menu"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
<field name="inherit_id" ref="website.website_visitor_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//button[@id='w_visitor_visit_counter']" position="before">
|
||||
<button name="%(website_event_online.event_registration_action_from_visitor)d"
|
||||
<button name="%(website_event.event_registration_action_from_visitor)d"
|
||||
type="action"
|
||||
class="oe_stat_button" icon="fa-ticket"
|
||||
attrs="{'invisible': [('event_registration_count', '=', 0)]}">
|
||||
@@ -11,11 +11,7 @@
|
||||
'website': 'https://www.odoo.com/page/events',
|
||||
'description': "",
|
||||
'depends': [
|
||||
# STD-TODO: remove 'website_event_track_online' we just need it to be
|
||||
# able to use 'website.event.menu' which is defined in 'website_event_track'
|
||||
# as well as even form view defined in track_online
|
||||
'website_event_online',
|
||||
'website_event_track_online',
|
||||
'website_event',
|
||||
'website_jitsi',
|
||||
],
|
||||
'demo': ['data/website_event_meet_demo.xml'],
|
||||
|
||||
@@ -8,13 +8,13 @@ from werkzeug.utils import redirect
|
||||
from odoo import exceptions, http
|
||||
from odoo.http import request
|
||||
from odoo.addons.http_routing.models.ir_http import slug
|
||||
from odoo.addons.website_event_track_online.controllers.community import WebsiteEventCommunityController
|
||||
from odoo.addons.website_event.controllers.community import EventCommunityController
|
||||
from odoo.osv import expression
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WebsiteEventMeetController(WebsiteEventCommunityController):
|
||||
class WebsiteEventMeetController(EventCommunityController):
|
||||
|
||||
def _get_event_rooms_base_domain(self, event):
|
||||
search_domain_base = [('event_id', '=', event.id), ('is_published', '=', True)]
|
||||
|
||||
@@ -7,4 +7,6 @@ from odoo import fields, models
|
||||
class EventMenu(models.Model):
|
||||
_inherit = "website.event.menu"
|
||||
|
||||
menu_type = fields.Selection(selection_add=[("meeting_room", "Event Meeting Room Menus")])
|
||||
menu_type = fields.Selection(
|
||||
selection_add=[("meeting_room", "Event Meeting Room Menus")],
|
||||
ondelete={'meeting_room': 'cascade'})
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<record id="event_event_view_form" model="ir.ui.view">
|
||||
<field name="name">event.event.view.form.inherit.meet</field>
|
||||
<field name="model">event.event</field>
|
||||
<field name="inherit_id" ref="website_event_track_online.event_event_view_form"/>
|
||||
<field name="inherit_id" ref="website_event.event_event_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='is_published']" position="before">
|
||||
<button class="oe_stat_button" context="{'default_event_id': active_id, 'search_default_event_id': active_id}" icon="fa-comments-o" name="%(event_meeting_room_action)d" type="action">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<record id="event_type_view_form" model="ir.ui.view">
|
||||
<field name="name">event.type.view.form.inherit.meet</field>
|
||||
<field name="model">event.type</field>
|
||||
<field name="inherit_id" ref="website_event.event_type_view_form_inherit_website"/>
|
||||
<field name="inherit_id" ref="website_event.event_type_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='website_menu']" position='after'>
|
||||
<div class="row mt16" attrs="{'invisible': [('website_menu', '=', False)]}">
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import http
|
||||
|
||||
from odoo.addons.website_event_track_online.controllers.community import WebsiteEventCommunityController
|
||||
from odoo.http import request
|
||||
from werkzeug.exceptions import Forbidden
|
||||
|
||||
from odoo import http
|
||||
from odoo.addons.website_event.controllers.community import EventCommunityController
|
||||
from odoo.http import request
|
||||
|
||||
class WebsiteEventTrackQuizMeetController(WebsiteEventCommunityController):
|
||||
|
||||
class WebsiteEventTrackQuizMeetController(EventCommunityController):
|
||||
|
||||
@http.route(['/event/<model("event.event"):event>/community'], type='http', auth="public", website=True, sitemap=False)
|
||||
def community(self, event, page=1, lang=None, **kwargs):
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import controllers
|
||||
from . import models
|
||||
from . import tests
|
||||
@@ -1,36 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
|
||||
{
|
||||
'name': 'Events Online',
|
||||
'category': 'Marketing/Events',
|
||||
'sequence': 1001,
|
||||
'version': '1.0',
|
||||
'summary': 'Bridge module to support Online Events',
|
||||
'website': 'https://www.odoo.com/page/events',
|
||||
'description': "",
|
||||
'depends': [
|
||||
'website_event'
|
||||
],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'security/event_security.xml',
|
||||
'views/assets.xml',
|
||||
'views/event_event_views.xml',
|
||||
'views/event_registration_views.xml',
|
||||
'views/event_tag_views.xml',
|
||||
'views/event_type_views.xml',
|
||||
'views/website_visitor_views.xml',
|
||||
'views/event_templates_event.xml',
|
||||
'views/event_templates_widgets.xml',
|
||||
],
|
||||
'demo': [
|
||||
'data/website_visitor_demo.xml',
|
||||
'data/event_demo.xml',
|
||||
'data/event_registration_demo.xml',
|
||||
'data/event_tag_demo.xml',
|
||||
],
|
||||
'application': False,
|
||||
'installable': True,
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import event_registration
|
||||
@@ -1,31 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.website_event.controllers.main import WebsiteEventController
|
||||
from odoo.http import request
|
||||
|
||||
|
||||
class WebsiteEventOnlineController(WebsiteEventController):
|
||||
|
||||
def _create_attendees_from_registration_post(self, event, registration_data):
|
||||
""" Override registration data to try to set a visitor (from request) and
|
||||
a partner (if visitor linked to a user for example). Purpose is to gather
|
||||
as much informations as possible, notably to ease future communications.
|
||||
Also try to update visitor informations based on registration info. """
|
||||
visitor_sudo = request.env['website.visitor']._get_visitor_from_request(force_create=True)
|
||||
visitor_sudo._update_visitor_last_visit()
|
||||
|
||||
if visitor_sudo:
|
||||
visitor_values = {}
|
||||
for info in registration_data:
|
||||
# registration may give a name to the visitor, yay
|
||||
if info.get('name') and not visitor_sudo.name and not visitor_values.get('name'):
|
||||
visitor_values['name'] = info['name']
|
||||
# update registration based on visitor
|
||||
info['visitor_id'] = visitor_sudo.id
|
||||
if not info.get('partner_id') and visitor_sudo.partner_id:
|
||||
info['partner_id'] = visitor_sudo.partner_id.id
|
||||
if visitor_values:
|
||||
visitor_sudo.write(visitor_values)
|
||||
|
||||
return super(WebsiteEventOnlineController, self)._create_attendees_from_registration_post(event, registration_data)
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="event.event_1" model="event.event">
|
||||
<field name="menu_register_cta" eval="True"/>
|
||||
</record>
|
||||
<record id="event.event_7" model="event.event">
|
||||
<field name="menu_register_cta" eval="True"/>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="event.event_tag_category_1_tag_1" model="event.tag">
|
||||
<field name="color">1</field>
|
||||
</record>
|
||||
<record id="event.event_tag_category_1_tag_2" model="event.tag">
|
||||
<field name="color">2</field>
|
||||
</record>
|
||||
<record id="event.event_tag_category_1_tag_3" model="event.tag">
|
||||
<field name="color">3</field>
|
||||
</record>
|
||||
<record id="event.event_tag_category_1_tag_4" model="event.tag">
|
||||
<field name="color">4</field>
|
||||
</record>
|
||||
|
||||
<record id="event.event_tag_category_2_tag_1" model="event.tag">
|
||||
<field name="color">5</field>
|
||||
</record>
|
||||
<record id="event.event_tag_category_2_tag_2" model="event.tag">
|
||||
<field name="color">6</field>
|
||||
</record>
|
||||
<record id="event.event_tag_category_2_tag_3" model="event.tag">
|
||||
<field name="color">7</field>
|
||||
</record>
|
||||
|
||||
<record id="event.event_tag_category_3_tag_1" model="event.tag">
|
||||
<field name="color">8</field>
|
||||
</record>
|
||||
<record id="event.event_tag_category_3_tag_2" model="event.tag">
|
||||
<field name="color">9</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -1,7 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import event_event
|
||||
from . import event_registration
|
||||
from . import event_tag
|
||||
from . import website_visitor
|
||||
@@ -1,132 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime
|
||||
from pytz import timezone, utc
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.addons.http_routing.models.ir_http import slug
|
||||
from odoo.addons.resource.models.resource import float_to_time
|
||||
from odoo.osv import expression
|
||||
|
||||
|
||||
class Event(models.Model):
|
||||
_name = 'event.event'
|
||||
_inherit = 'event.event'
|
||||
|
||||
# frontend options
|
||||
menu_register_cta = fields.Boolean(
|
||||
'Add Register Button', compute='_compute_menu_register_cta',
|
||||
readonly=False, store=True)
|
||||
# live information
|
||||
is_ongoing = fields.Boolean(
|
||||
'Is Ongoing', compute='_compute_time_data', search='_search_is_ongoing',
|
||||
help="Whether event has begun")
|
||||
is_done = fields.Boolean(
|
||||
'Is Done', compute='_compute_time_data',
|
||||
help="Whether event is finished")
|
||||
start_today = fields.Boolean(
|
||||
'Start Today', compute='_compute_time_data',
|
||||
help="Whether event is going to start today if still not ongoing")
|
||||
start_remaining = fields.Integer(
|
||||
'Remaining before start', compute='_compute_time_data',
|
||||
help="Remaining time before event starts (minutes)")
|
||||
|
||||
@api.depends('website_menu')
|
||||
def _compute_menu_register_cta(self):
|
||||
for event in self:
|
||||
event.menu_register_cta = event.website_menu
|
||||
|
||||
@api.depends('date_begin', 'date_end')
|
||||
def _compute_time_data(self):
|
||||
""" Compute start and remaining time. Do everything in UTC as we compute only
|
||||
time deltas here. """
|
||||
now_utc = utc.localize(fields.Datetime.now().replace(microsecond=0))
|
||||
for event in self:
|
||||
date_begin_utc = utc.localize(event.date_begin, is_dst=False)
|
||||
date_end_utc = utc.localize(event.date_end, is_dst=False)
|
||||
event.is_ongoing = date_begin_utc <= now_utc <= date_end_utc
|
||||
event.is_done = now_utc > date_end_utc
|
||||
event.start_today = date_begin_utc.date() == now_utc.date()
|
||||
if date_begin_utc >= now_utc:
|
||||
td = date_begin_utc - now_utc
|
||||
event.start_remaining = int(td.total_seconds() / 60)
|
||||
else:
|
||||
event.start_remaining = 0
|
||||
|
||||
def _compute_is_participating(self):
|
||||
""" Override is_participating to improve heuristic that is now
|
||||
|
||||
* public, no visitor: not participating as we have no information;
|
||||
* public and visitor: check visitor is linked to a registration. As
|
||||
visitors are merged on the top parent, current visitor check is
|
||||
sufficient even for successive visits;
|
||||
* logged, no visitor: check partner is linked to a registration. Do
|
||||
not check the email as it is not really secure;
|
||||
* logged as visitor: check partner or visitor are linked to a
|
||||
registration;
|
||||
"""
|
||||
current_visitor = self.env['website.visitor']._get_visitor_from_request(force_create=False)
|
||||
if self.env.user._is_public() and not current_visitor:
|
||||
events = self.env['event.event']
|
||||
elif self.env.user._is_public():
|
||||
events = self.env['event.registration'].sudo().search([
|
||||
('event_id', 'in', self.ids),
|
||||
('visitor_id', '=', current_visitor.id),
|
||||
]).event_id
|
||||
else:
|
||||
if 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)]
|
||||
events = self.env['event.registration'].sudo().search(
|
||||
expression.AND([
|
||||
domain,
|
||||
[('event_id', 'in', self.ids)]
|
||||
])
|
||||
).event_id
|
||||
|
||||
|
||||
for event in self:
|
||||
event.is_participating = event in events
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# WEBSITE MENU MANAGEMENT
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def _get_menu_entries(self):
|
||||
""" Deprecate old method as a new one with more information is used
|
||||
starting with website_event_online module. """
|
||||
return []
|
||||
|
||||
def _get_menu_entries_online(self):
|
||||
""" Method returning menu entries to display on the website view of the
|
||||
event, possibly depending on some options in inheriting modules.
|
||||
|
||||
Each menu entry is a tuple containing :
|
||||
* name: menu item name
|
||||
* url: if set, url to a route (do not use xml_id in that case);
|
||||
* xml_id: template linked to the page (do not use url in that case);
|
||||
* sequence: specific sequence of menu entry to be set on the menu;
|
||||
* menu_type: type of menu entry (used in inheriting modules to ease
|
||||
menu management; not used in this module in 13.3 due to technical
|
||||
limitations);
|
||||
"""
|
||||
self.ensure_one()
|
||||
return [
|
||||
(_('Introduction'), False, 'website_event.template_intro', 1, False),
|
||||
(_('Location'), False, 'website_event.template_location', 50, False),
|
||||
(_('Register'), '/event/%s/register' % slug(self), False, 100, False),
|
||||
]
|
||||
|
||||
def _update_website_menus(self, menus_update_by_field=None):
|
||||
""" Synchronize event configuration and its menu entries for frontend. """
|
||||
super(Event, self)._update_website_menus(menus_update_by_field=menus_update_by_field)
|
||||
for event in self:
|
||||
if event.website_menu and (not menus_update_by_field or event in menus_update_by_field.get('website_menu')):
|
||||
for name, url, xml_id, menu_sequence, menu_type in event._get_menu_entries_online():
|
||||
event._create_menu(menu_sequence, name, url, xml_id, menu_type=menu_type)
|
||||
@@ -1,17 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from random import randint
|
||||
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class EventTag(models.Model):
|
||||
_inherit = ['event.tag']
|
||||
|
||||
def _default_color(self):
|
||||
return randint(1, 11)
|
||||
|
||||
color = fields.Integer(
|
||||
string='Color Index', default=lambda self: self._default_color(),
|
||||
help='Tag color. No color means no display in kanban or front-end, to distinguish internal tags from public categorization tags.')
|
||||
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo><data noupdate="1">
|
||||
|
||||
<record id="ir_rule_event_tag_public" model="ir.rule">
|
||||
<field name="name">Event Tag: public/portal: color = published</field>
|
||||
<field name="model_id" ref="event.model_event_tag"/>
|
||||
<field name="domain_force">['&', ('color', '!=', False), ('color', '!=', 0)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_public')), (4, ref('base.group_portal'))]"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_unlink" eval="False"/>
|
||||
</record>
|
||||
|
||||
</data></odoo>
|
||||
@@ -1,2 +0,0 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
website_visitor_access_event_user,website.visitor.access.event.user,model_website_visitor,event.group_event_user,1,1,0,0
|
||||
|
@@ -1,5 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import common
|
||||
from . import test_visitor_events
|
||||
@@ -1,68 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime, timedelta, time
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.addons.event.tests.common import TestEventCommon
|
||||
from odoo.fields import Datetime as FieldsDatetime, Date as FieldsDate
|
||||
from odoo.tests.common import SavepointCase
|
||||
|
||||
|
||||
class EventDtPatcher(SavepointCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(EventDtPatcher, cls).setUpClass()
|
||||
|
||||
cls.reference_now = datetime(2020, 7, 6, 10, 0, 0)
|
||||
cls.reference_today = datetime(2020, 7, 6)
|
||||
|
||||
cls.event_dt = patch(
|
||||
'odoo.addons.event.models.event_event.fields.Datetime',
|
||||
wraps=FieldsDatetime
|
||||
)
|
||||
cls.wevent_main_dt = patch(
|
||||
'odoo.addons.website_event.controllers.main.fields.Datetime',
|
||||
wraps=FieldsDatetime
|
||||
)
|
||||
cls.event_online_dt = patch(
|
||||
'odoo.addons.website_event_online.models.event_event.fields.Datetime',
|
||||
wraps=FieldsDatetime
|
||||
)
|
||||
cls.event_date = patch(
|
||||
'odoo.addons.event.models.event_event.fields.Date',
|
||||
wraps=FieldsDate
|
||||
)
|
||||
cls.wevent_main_date = patch(
|
||||
'odoo.addons.website_event.controllers.main.fields.Date',
|
||||
wraps=FieldsDate
|
||||
)
|
||||
cls.mock_event_dt = cls.event_dt.start()
|
||||
cls.mock_wevent_main_dt = cls.wevent_main_dt.start()
|
||||
cls.mock_event_online_dt = cls.event_online_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_main_dt.now.return_value = cls.reference_now
|
||||
cls.mock_event_online_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_main_dt.stop)
|
||||
cls.addClassCleanup(cls.event_online_dt.stop)
|
||||
cls.addClassCleanup(cls.event_date.stop)
|
||||
cls.addClassCleanup(cls.wevent_main_date.stop)
|
||||
|
||||
|
||||
class TestEventOnlineCommon(TestEventCommon, EventDtPatcher):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestEventOnlineCommon, cls).setUpClass()
|
||||
|
||||
# event if 8-18 in Europe/Brussels (DST) (first day: begins at 9, last day: ends at 15)
|
||||
cls.event_0.write({
|
||||
'date_begin': datetime.combine(cls.reference_now, time(7, 0)) - timedelta(days=1),
|
||||
'date_end': datetime.combine(cls.reference_now, time(13, 0)) + timedelta(days=1),
|
||||
})
|
||||
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<template id="assets_frontend" inherit_id="website.assets_frontend" name="Event Online Assets Frontend">
|
||||
<xpath expr="//link[last()]" position="after">
|
||||
<link rel="stylesheet" type="text/scss" href="/website_event_online/static/src/scss/website_event_templates.scss"/>
|
||||
</xpath>
|
||||
<xpath expr="//script[last()]" position="after">
|
||||
<script type="text/javascript" src="/website_event_online/static/src/js/display_timer_widget.js"></script>
|
||||
<script type="text/javascript" src="/website_event_online/static/src/js/register_toaster_widget.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
@@ -1,23 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo><data>
|
||||
|
||||
<record id="event_event_view_form" model="ir.ui.view">
|
||||
<field name="name">event.event.view.form.inherit.online</field>
|
||||
<field name="model">event.event</field>
|
||||
<field name="inherit_id" ref="event.view_event_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[hasclass('oe_title')]" position="after">
|
||||
<div name="event_menu_configuration" groups="base.group_no_one">
|
||||
<label for="website_menu" string="Website Submenu"/>
|
||||
<field name="website_menu"/>
|
||||
<label for="menu_register_cta" string="Register Button"/>
|
||||
<field name="menu_register_cta"/>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='tag_ids']" position="attributes">
|
||||
<attribute name="options">{'color_field': 'color', 'no_quick_create': True}</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data></odoo>
|
||||
@@ -1,48 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo><data>
|
||||
<record id="event_tag_category_view_form" model="ir.ui.view">
|
||||
<field name="name">event.tag.category.view.form.inherit.online</field>
|
||||
<field name="model">event.tag.category</field>
|
||||
<field name="inherit_id" ref="event.event_tag_category_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='tag_ids']//field[@name='name']" position="after">
|
||||
<field name="color" widget="color_picker"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="event_tag_category_view_list" model="ir.ui.view">
|
||||
<field name="name">event.tag.category.view.tree.inherit.online</field>
|
||||
<field name="model">event.tag.category</field>
|
||||
<field name="inherit_id" ref="event.event_tag_category_view_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='tag_ids']" position="attributes">
|
||||
<attribute name="widget">many2many_tags</attribute>
|
||||
<attribute name="options">{'color_field': 'color'}</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="event_tag_view_list" model="ir.ui.view">
|
||||
<field name="name">event.tag.view.list.inherit.online</field>
|
||||
<field name="model">event.tag</field>
|
||||
<field name="inherit_id" ref="event.event_tag_view_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='category_id']" position="after">
|
||||
<field name="color" widget="color_picker"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="event_tag_view_form" model="ir.ui.view">
|
||||
<field name="name">event.tag.view.form.inherit.online</field>
|
||||
<field name="model">event.tag</field>
|
||||
<field name="inherit_id" ref="event.event_tag_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='category_id']" position="after">
|
||||
<field name="color" widget="color_picker"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data></odoo>
|
||||
@@ -1,54 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- Add Register additional CTA button, in addition to menus -->
|
||||
<template id="event_layout_main" inherit_id="website_event.layout">
|
||||
<xpath expr="//div[@id='o_wevent_event_submenu']/ul" position="after">
|
||||
<a t-if="event.menu_register_cta and not event.is_participating"
|
||||
t-att-href="'/event/%s/register' % (slug(event))"
|
||||
class="btn btn-primary ml-auto">
|
||||
Register
|
||||
</a>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<!-- Support tag colors -->
|
||||
<template id="events_list" name="Events List (Online)"
|
||||
inherit_id="website_event.events_list">
|
||||
<xpath expr="//t[@t-foreach='event.tag_ids']/span" position="replace">
|
||||
<span t-if="tag.color" t-attf-class="badge mr8 #{'badge-primary' if tag in search_tags else 'badge-light'} #{'o_tag_color_%s' % tag.color if tag.color else ''}">
|
||||
<span t-esc="tag.name"/>
|
||||
</span>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="event_category_tag" name="Filter by Tags (Online)"
|
||||
inherit_id="website_event.event_category_tag">
|
||||
<xpath expr="//t[@t-foreach='categories']" position="replace">
|
||||
<t t-foreach="categories" t-as="category">
|
||||
<li t-if="category.tag_ids and any(tag.color for tag in category.tag_ids)" class="nav-item dropdown mr-2 my-1">
|
||||
<a href="#" role="button" class="btn dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="fa fa-folder-open"/>
|
||||
<t t-esc="category.name"/>
|
||||
</a>
|
||||
<div class="dropdown-menu">
|
||||
<t t-foreach="category.tag_ids" t-as="tag">
|
||||
<a t-if="tag.color"
|
||||
t-att-href="'/event?%s' % keep_query('*', tags=str((search_tags - tag).ids if tag in search_tags else (tag | search_tags).ids))"
|
||||
t-attf-class="dropdown-item d-flex align-items-center justify-content-between #{'active' if tag in search_tags else ''}">
|
||||
<t t-esc="tag.name"/>
|
||||
</a>
|
||||
</t>
|
||||
</div>
|
||||
</li>
|
||||
</t>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="registration_template" inherit_id="website_event.registration_template">
|
||||
<xpath expr="//div" position="after">
|
||||
<div t-if="toast_message" class="o_wevent_register_toaster d-none" t-att-data-message="toast_message"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
@@ -1,29 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<template id="display_timer_widget" name="Display Timer Widget">
|
||||
<t t-set="pre_countdown_display" t-value="bool(pre_countdown_text) or pre_countdown_display"/>
|
||||
<t t-set="pre_countdown_time" t-value="datetime.datetime.now().timestamp() + int(pre_remaining_time)"/>
|
||||
|
||||
<div class="o_display_timer"
|
||||
t-att-data-display-class="display_class"
|
||||
t-att-data-main-countdown-time="datetime.datetime.now().timestamp() + int(main_remaining_time)"
|
||||
t-att-data-main-countdown-text="main_countdown_text"
|
||||
t-att-data-main-countdown-display="main_countdown_display"
|
||||
t-att-data-pre-countdown-time="pre_countdown_time"
|
||||
t-att-data-pre-countdown-display="pre_countdown_display"
|
||||
t-att-data-pre-countdown-text="pre_countdown_text">
|
||||
<t t-set="remaining_time" t-value="pre_remaining_time if pre_remaining_time else main_remaining_time"/>
|
||||
<span class="o_display_timer_countdown d-flex justify-content-center">
|
||||
<span class="o_countdown_text pr-1" t-esc="pre_countdown_text if pre_countdown_text else main_countdown_text if not pre_countdown_display else ''"/>
|
||||
<div t-if="int(remaining_time) > 86400"
|
||||
class="o_countdown_metric_container"><span class="o_countdown_remaining o_timer_days pr-1">0</span><span class="o_countdown_metric pr-1">days</span></div>
|
||||
<div t-if="int(remaining_time) > 3600"
|
||||
class="o_countdown_metric_container"><span class="o_countdown_remaining o_timer_hours">00</span><span class="o_countdown_metric">:</span></div>
|
||||
<div class="o_countdown_metric_container"><span class="o_countdown_remaining o_timer_minutes">00</span><span class="o_countdown_metric">:</span></div>
|
||||
<div class="o_countdown_metric_container"><span class="o_countdown_remaining o_timer_seconds">00</span><span class="o_countdown_metric"></span></div>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo><data>
|
||||
|
||||
<record id="event_type_view_form" model="ir.ui.view" >
|
||||
<field name="name">event.type.view.form.inherit.online</field>
|
||||
<field name="model">event.type</field>
|
||||
<field name="inherit_id" ref="event.view_event_type_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='tag_ids']" position="attributes">
|
||||
<attribute name="options">{'color_field': 'color', 'no_quick_create': True}</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data></odoo>
|
||||
@@ -3,7 +3,7 @@
|
||||
<record id="event_type_view_form_inherit_question" model="ir.ui.view">
|
||||
<field name="name">event.type.view.form.inherit.question</field>
|
||||
<field name="model">event.type</field>
|
||||
<field name="inherit_id" ref="website_event.event_type_view_form_inherit_website"/>
|
||||
<field name="inherit_id" ref="website_event.event_type_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<div name="event_type_attendees_auto_confirm" position="after">
|
||||
<div class="col-12 col-lg-12 o_setting_box">
|
||||
@@ -28,10 +28,10 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="event_event_view_form_inherit_question" model="ir.ui.view">
|
||||
<record id="event_event_view_form" model="ir.ui.view">
|
||||
<field name="name">event.event.view.form.inherit.question</field>
|
||||
<field name="model">event.event</field>
|
||||
<field name="inherit_id" ref="website_event.event_event_view_form_inherit_website"/>
|
||||
<field name="inherit_id" ref="website_event.event_event_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<page name="event_notes" position="before">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
'name': 'Advanced Events',
|
||||
'category': 'Marketing',
|
||||
'summary': 'Sponsors, Tracks, Agenda, Event News',
|
||||
'version': '1.0',
|
||||
'version': '1.1',
|
||||
'description': "",
|
||||
'depends': ['website_event'],
|
||||
'data': [
|
||||
@@ -13,18 +13,33 @@
|
||||
'security/event_track_security.xml',
|
||||
'data/event_data.xml',
|
||||
'data/event_track_data.xml',
|
||||
'views/event_track_templates.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',
|
||||
'views/event_track_views.xml',
|
||||
'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_views.xml',
|
||||
'views/website_event_menu_views.xml',
|
||||
'views/event_event_views.xml',
|
||||
'views/event_type_views.xml',
|
||||
'views/website_visitor_views.xml',
|
||||
'views/event_menus.xml',
|
||||
],
|
||||
'demo': [
|
||||
'data/event_demo.xml',
|
||||
'data/event_sponsor_demo.xml',
|
||||
'data/event_track_demo_misc.xml',
|
||||
'data/event_track_location_demo.xml',
|
||||
'data/event_track_tag_demo.xml',
|
||||
'data/event_track_demo.xml',
|
||||
'data/event_track_demo_description.xml',
|
||||
'data/event_track_visitor_demo.xml',
|
||||
],
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,477 @@
|
||||
# -*- 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
|
||||
import babel.dates
|
||||
import base64
|
||||
import pytz
|
||||
|
||||
from odoo import exceptions, http, fields, _
|
||||
from odoo.http import request
|
||||
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
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@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_session",
|
||||
self._event_tracks_get_values(event, tag=tag, **searches)
|
||||
)
|
||||
|
||||
def _event_tracks_get_values(self, event, tag=None, **searches):
|
||||
# init and process search terms
|
||||
searches.setdefault('search', '')
|
||||
searches.setdefault('search_wishlist', '')
|
||||
searches.setdefault('tags', '')
|
||||
search_domain = self._get_event_tracks_base_domain(event)
|
||||
|
||||
# search on content
|
||||
if searches.get('search'):
|
||||
search_domain = expression.AND([
|
||||
search_domain,
|
||||
[('name', 'ilike', searches['search'])]
|
||||
])
|
||||
|
||||
# search on tags
|
||||
search_tags = self._get_search_tags(searches['tags'])
|
||||
if not search_tags and tag: # backward compatibility
|
||||
search_tags = tag
|
||||
if search_tags:
|
||||
# Example: You filter on age: 10-12 and activity: football.
|
||||
# Doing it this way allows to only get events who are tagged "age: 10-12" AND "activity: football".
|
||||
# Add another tag "age: 12-15" to the search and it would fetch the ones who are tagged:
|
||||
# ("age: 10-12" OR "age: 12-15") AND "activity: football
|
||||
grouped_tags = dict()
|
||||
for search_tag in search_tags:
|
||||
grouped_tags.setdefault(search_tag.category_id, list()).append(search_tag)
|
||||
search_domain_items = [
|
||||
[('tag_ids', 'in', [tag.id for tag in grouped_tags[group]])]
|
||||
for group in grouped_tags
|
||||
]
|
||||
search_domain = expression.AND([
|
||||
search_domain,
|
||||
*search_domain_items
|
||||
])
|
||||
|
||||
# fetch data to display with TZ set for both event and tracks
|
||||
now_tz = utc.localize(fields.Datetime.now().replace(microsecond=0), is_dst=False).astimezone(timezone(event.date_tz))
|
||||
today_tz = now_tz.date()
|
||||
event = event.with_context(tz=event.date_tz or 'UTC')
|
||||
tracks_sudo = event.env['event.track'].sudo().search(search_domain, order='date asc')
|
||||
tag_categories = request.env['event.track.tag.category'].sudo().search([])
|
||||
|
||||
# filter on wishlist (as post processing due to costly search on is_reminder_on)
|
||||
if searches.get('search_wishlist'):
|
||||
tracks_sudo = tracks_sudo.filtered(lambda track: track.is_reminder_on)
|
||||
|
||||
# organize categories for display: announced, live, soon and day-based
|
||||
tracks_announced = tracks_sudo.filtered(lambda track: not track.date)
|
||||
tracks_wdate = tracks_sudo - tracks_announced
|
||||
date_begin_tz_all = list(set(
|
||||
dt.date()
|
||||
for dt in self._get_dt_in_event_tz(tracks_wdate.mapped('date'), event)
|
||||
))
|
||||
date_begin_tz_all.sort()
|
||||
tracks_sudo_live = tracks_wdate.filtered(lambda track: track.is_published and track.is_track_live)
|
||||
tracks_sudo_soon = tracks_wdate.filtered(lambda track: track.is_published and not track.is_track_live and track.is_track_soon)
|
||||
tracks_by_day = []
|
||||
for display_date in date_begin_tz_all:
|
||||
matching_tracks = tracks_wdate.filtered(lambda track: self._get_dt_in_event_tz([track.date], event)[0].date() == display_date)
|
||||
tracks_by_day.append({'date': display_date, 'name': display_date, 'tracks': matching_tracks})
|
||||
if tracks_announced:
|
||||
tracks_announced = tracks_announced.sorted('wishlisted_by_default', reverse=True)
|
||||
tracks_by_day.append({'date': False, 'name': _('Coming soon'), 'tracks': tracks_announced})
|
||||
|
||||
# return rendering values
|
||||
return {
|
||||
# event information
|
||||
'event': event,
|
||||
'main_object': event,
|
||||
# tracks display information
|
||||
'tracks': tracks_sudo,
|
||||
'tracks_by_day': tracks_by_day,
|
||||
'tracks_live': tracks_sudo_live,
|
||||
'tracks_soon': tracks_sudo_soon,
|
||||
'today_tz': today_tz,
|
||||
# search information
|
||||
'searches': searches,
|
||||
'search_key': searches['search'],
|
||||
'search_wishlist': searches['search_wishlist'],
|
||||
'search_tags': search_tags,
|
||||
'tag_categories': tag_categories,
|
||||
# environment
|
||||
'is_html_empty': is_html_empty,
|
||||
'hostname': request.httprequest.host.split(':')[0],
|
||||
'user_event_manager': request.env.user.has_group('event.group_event_manager'),
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# AGENDA VIEW
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@http.route(['''/event/<model("event.event"):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')
|
||||
vals = {
|
||||
'event': event,
|
||||
'main_object': event,
|
||||
'tag': tag,
|
||||
'user_event_manager': request.env.user.has_group('event.group_event_manager'),
|
||||
}
|
||||
|
||||
vals.update(self._prepare_calendar_values(event))
|
||||
|
||||
return request.render("website_event_track.agenda_online", vals)
|
||||
|
||||
def _prepare_calendar_values(self, event):
|
||||
"""
|
||||
Override that should completely replace original method in v14.
|
||||
|
||||
This methods slit the day (max end time - min start time) into 15 minutes time slots.
|
||||
For each time slot, we assign the tracks that start at this specific time slot, and we add the number
|
||||
of time slot that the track covers (track duration / 15 min)
|
||||
The calendar will be divided into rows of 15 min, and the talks will cover the corresponding number of rows
|
||||
(15 min slots).
|
||||
"""
|
||||
event = event.with_context(tz=event.date_tz or 'UTC')
|
||||
local_tz = pytz.timezone(event.date_tz or 'UTC')
|
||||
lang_code = request.env.context.get('lang')
|
||||
event_track_ids = self._event_agenda_get_tracks(event)
|
||||
|
||||
locations = list(set(track.location_id for track in event_track_ids))
|
||||
locations.sort(key=lambda x: x.id)
|
||||
|
||||
# First split day by day (based on start time)
|
||||
time_slots_by_tracks = {track: self._split_track_by_days(track, local_tz) for track in event_track_ids}
|
||||
|
||||
# extract all the tracks time slots
|
||||
track_time_slots = set().union(*(time_slot.keys() for time_slot in [time_slots for time_slots in time_slots_by_tracks.values()]))
|
||||
|
||||
# extract unique days
|
||||
days = list(set(time_slot.date() for time_slot in track_time_slots))
|
||||
days.sort()
|
||||
|
||||
# Create the dict that contains the tracks at the correct time_slots / locations coordinates
|
||||
tracks_by_days = dict.fromkeys(days, 0)
|
||||
time_slots_by_day = dict((day, dict(start=set(), end=set())) for day in days)
|
||||
tracks_by_rounded_times = dict((time_slot, dict((location, {}) for location in locations)) for time_slot in track_time_slots)
|
||||
for track, time_slots in time_slots_by_tracks.items():
|
||||
start_date = fields.Datetime.from_string(track.date).replace(tzinfo=pytz.utc).astimezone(local_tz)
|
||||
end_date = start_date + timedelta(hours=(track.duration or 0.25))
|
||||
|
||||
for time_slot, duration in time_slots.items():
|
||||
tracks_by_rounded_times[time_slot][track.location_id][track] = {
|
||||
'rowspan': duration, # rowspan
|
||||
'start_date': self._get_locale_time(start_date, lang_code),
|
||||
'end_date': self._get_locale_time(end_date, lang_code),
|
||||
'occupied_cells': self._get_occupied_cells(track, duration, locations, local_tz)
|
||||
}
|
||||
|
||||
# get all the time slots by day to determine the max duration of a day.
|
||||
day = time_slot.date()
|
||||
time_slots_by_day[day]['start'].add(time_slot)
|
||||
time_slots_by_day[day]['end'].add(time_slot+timedelta(minutes=15*duration))
|
||||
tracks_by_days[day] += 1
|
||||
|
||||
# split days into 15 minutes time slots
|
||||
global_time_slots_by_day = dict((day, {}) for day in days)
|
||||
for day, time_slots in time_slots_by_day.items():
|
||||
start_time_slot = min(time_slots['start'])
|
||||
end_time_slot = max(time_slots['end'])
|
||||
|
||||
time_slots_count = int(((end_time_slot - start_time_slot).total_seconds() / 3600) * 4)
|
||||
current_time_slot = start_time_slot
|
||||
for i in range(0, time_slots_count + 1):
|
||||
global_time_slots_by_day[day][current_time_slot] = tracks_by_rounded_times.get(current_time_slot, {})
|
||||
global_time_slots_by_day[day][current_time_slot]['formatted_time'] = self._get_locale_time(current_time_slot, lang_code)
|
||||
current_time_slot = current_time_slot + timedelta(minutes=15)
|
||||
|
||||
# count the number of tracks by days
|
||||
tracks_by_days = dict.fromkeys(days, 0)
|
||||
for track in event_track_ids:
|
||||
track_day = fields.Datetime.from_string(track.date).replace(tzinfo=pytz.utc).astimezone(local_tz).date()
|
||||
tracks_by_days[track_day] += 1
|
||||
|
||||
return {
|
||||
'days': days,
|
||||
'tracks_by_days': tracks_by_days,
|
||||
'time_slots': global_time_slots_by_day,
|
||||
'locations': locations
|
||||
}
|
||||
|
||||
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/<model("event.event", "[('website_track', '=', True)]"):event>/track/<model("event.track", "[('event_id', '=', event.id)]"):track>''',
|
||||
type='http', auth="public", website=True, sitemap=True)
|
||||
def event_track_page(self, event, track, **options):
|
||||
track = self._fetch_track(track.id, allow_is_accepted=False)
|
||||
|
||||
return request.render(
|
||||
"website_event_track.event_track_main",
|
||||
self._event_track_page_get_values(event, track.sudo(), **options)
|
||||
)
|
||||
|
||||
def _event_track_page_get_values(self, event, track, **options):
|
||||
track = track.sudo()
|
||||
|
||||
option_widescreen = options.get('widescreen', False)
|
||||
option_widescreen = bool(option_widescreen) if option_widescreen != '0' else False
|
||||
# search for tracks list
|
||||
tracks_other = track._get_track_suggestions(
|
||||
restrict_domain=self._get_event_tracks_base_domain(track.event_id),
|
||||
limit=10
|
||||
)
|
||||
|
||||
return {
|
||||
# event information
|
||||
'event': event,
|
||||
'main_object': track,
|
||||
'track': track,
|
||||
# sidebar
|
||||
'tracks_other': tracks_other,
|
||||
# options
|
||||
'option_widescreen': option_widescreen,
|
||||
# environment
|
||||
'is_html_empty': is_html_empty,
|
||||
'hostname': request.httprequest.host.split(':')[0],
|
||||
'user_event_manager': request.env.user.has_group('event.group_event_manager'),
|
||||
}
|
||||
|
||||
@http.route("/event/track/toggle_reminder", type="json", auth="public", website=True)
|
||||
def track_reminder_toggle(self, track_id, set_reminder_on):
|
||||
""" Set a reminder a track for current visitor. Track visitor is created or updated
|
||||
if it already exists. Exception made if un-wishlisting and no track_visitor
|
||||
record found (should not happen unless manually done).
|
||||
|
||||
:param boolean set_reminder_on:
|
||||
If True, set as a wishlist, otherwise un-wishlist track;
|
||||
If the track is a Key Track (wishlisted_by_default):
|
||||
if set_reminder_on = False, blacklist the track_partner
|
||||
otherwise, un-blacklist the track_partner
|
||||
"""
|
||||
track = self._fetch_track(track_id, allow_is_accepted=True)
|
||||
force_create = set_reminder_on or track.wishlisted_by_default
|
||||
event_track_partner = track._get_event_track_visitors(force_create=force_create)
|
||||
visitor_sudo = event_track_partner.visitor_id
|
||||
|
||||
if not track.wishlisted_by_default:
|
||||
if not event_track_partner or event_track_partner.is_wishlisted == set_reminder_on: # ignore if new state = old state
|
||||
return {'error': 'ignored'}
|
||||
event_track_partner.is_wishlisted = set_reminder_on
|
||||
else:
|
||||
if not event_track_partner or event_track_partner.is_blacklisted != set_reminder_on: # ignore if new state = old state
|
||||
return {'error': 'ignored'}
|
||||
event_track_partner.is_blacklisted = not set_reminder_on
|
||||
|
||||
result = {'reminderOn': set_reminder_on}
|
||||
if request.httprequest.cookies.get('visitor_uuid', '') != visitor_sudo.access_token:
|
||||
result['visitor_uuid'] = visitor_sudo.access_token
|
||||
|
||||
return result
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# TRACK PROPOSAL
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@http.route(['''/event/<model("event.event"):event>/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/<model("event.event"):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
|
||||
|
||||
def _get_search_tags(self, tag_search):
|
||||
# TDE FIXME: make me generic (slides, event, ...)
|
||||
try:
|
||||
tag_ids = literal_eval(tag_search)
|
||||
except Exception:
|
||||
tags = request.env['event.track.tag'].sudo()
|
||||
else:
|
||||
# perform a search to filter on existing / valid tags implicitly
|
||||
tags = request.env['event.track.tag'].sudo().search([('id', 'in', tag_ids)])
|
||||
return tags
|
||||
|
||||
def _get_dt_in_event_tz(self, datetimes, event):
|
||||
tz_name = event.date_tz
|
||||
return [
|
||||
utc.localize(dt, is_dst=False).astimezone(timezone(tz_name))
|
||||
for dt in datetimes
|
||||
]
|
||||
@@ -1,169 +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):
|
||||
|
||||
@http.route(['''/event/<model("event.event"):event>/track/<model("event.track", "[('event_id','=',event.id)]"):track>'''], type='http', auth="public", website=True, sitemap=True)
|
||||
def event_track_view(self, event, track, **post):
|
||||
if not event.can_access_from_current_website():
|
||||
raise NotFound()
|
||||
|
||||
track = track.sudo().with_context(tz=event.date_tz or 'UTC')
|
||||
values = {'track': track, 'event': track.event_id, 'main_object': track}
|
||||
return request.render("website_event_track.track_view", values)
|
||||
|
||||
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/<model("event.event"):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
|
||||
|
||||
@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, **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)
|
||||
|
||||
@http.route(['''/event/<model("event.event"):event>/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/<model("event.event"):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})
|
||||
@@ -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
|
||||
@@ -33,14 +33,17 @@
|
||||
<record id="event_sponsor_type1" model="event.sponsor.type">
|
||||
<field name="name">Bronze</field>
|
||||
<field name="sequence">3</field>
|
||||
<field name="display_ribbon_style">Bronze</field>
|
||||
</record>
|
||||
<record id="event_sponsor_type2" model="event.sponsor.type">
|
||||
<field name="name">Silver</field>
|
||||
<field name="sequence">2</field>
|
||||
<field name="display_ribbon_style">Silver</field>
|
||||
</record>
|
||||
<record id="event_sponsor_type3" model="event.sponsor.type">
|
||||
<field name="name">Gold</field>
|
||||
<field name="sequence">1</field>
|
||||
<field name="display_ribbon_style">Gold</field>
|
||||
</record>
|
||||
|
||||
<record id="event_track_stage0" model="event.track.stage">
|
||||
@@ -58,15 +61,19 @@
|
||||
<field name="name">Announced</field>
|
||||
<field name="sequence">3</field>
|
||||
<field name="color">3</field>
|
||||
<field name="is_accepted" eval="True"/>
|
||||
</record>
|
||||
<record id="event_track_stage3" model="event.track.stage">
|
||||
<field name="name">Published</field>
|
||||
<field name="sequence">4</field>
|
||||
<field name="color">4</field>
|
||||
<field name="is_accepted" eval="True"/>
|
||||
<field name="is_done" eval="True"/>
|
||||
</record>
|
||||
<record id="event_track_stage4" model="event.track.stage">
|
||||
<field name="name">Refused</field>
|
||||
<field name="sequence">5</field>
|
||||
<field name="color">5</field>
|
||||
<field name="fold" eval="True"/>
|
||||
</record>
|
||||
<record id="event_track_stage5" model="event.track.stage">
|
||||
|
||||
@@ -333,6 +333,7 @@
|
||||
<field name="color">1</field>
|
||||
<field name="event_id" ref="event.event_7"/>
|
||||
<field name="stage_id" ref="event_track_stage3"/>
|
||||
<field name="wishlisted_by_default" eval="True"/>
|
||||
<field name="date" eval="(DateTime.now() - timedelta(days=1)).strftime('%Y-%m-%d 06:00:00')"></field>
|
||||
<field name="tag_ids" eval="[
|
||||
(4, ref('website_event_track.event_track_tag1')),
|
||||
@@ -343,6 +344,10 @@
|
||||
<field name="duration">2</field>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="partner_id" ref="base.res_partner_address_15"/>
|
||||
<field name="website_cta" eval="True"/>
|
||||
<field name="website_cta_title">Try Now</field>
|
||||
<field name="website_cta_url">http://www.example.com</field>
|
||||
<field name="website_cta_delay">10</field>
|
||||
</record>
|
||||
<record id="event_7_track_2" model="event.track">
|
||||
<field name="name">First Day Wrapup</field>
|
||||
@@ -386,6 +391,10 @@
|
||||
<field name="duration">2</field>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="partner_id" ref="base.res_partner_address_15"/>
|
||||
<field name="website_cta" eval="True"/>
|
||||
<field name="website_cta_title">Try Now</field>
|
||||
<field name="website_cta_url">http://www.example.com</field>
|
||||
<field name="website_cta_delay">10</field>
|
||||
</record>
|
||||
<!-- Location 2 -->
|
||||
<record id="event_7_track_5" model="event.track">
|
||||
@@ -474,6 +483,7 @@
|
||||
<field name="color">1</field>
|
||||
<field name="event_id" ref="event.event_7"/>
|
||||
<field name="stage_id" ref="event_track_stage3"/>
|
||||
<field name="wishlisted_by_default" eval="True"/>
|
||||
<field name="date" eval="DateTime.now().strftime('%Y-%m-%d 06:00:00')"></field>
|
||||
<field name="tag_ids" eval="[
|
||||
(4, ref('website_event_track.event_track_tag1')),
|
||||
@@ -519,6 +529,7 @@
|
||||
<field name="color">2</field>
|
||||
<field name="event_id" ref="event.event_7"/>
|
||||
<field name="stage_id" ref="event_track_stage3"/>
|
||||
<field name="wishlisted_by_default" eval="True"/>
|
||||
<field name="location_id" ref="website_event_track.event_track_location_online_1"/>
|
||||
<field name="date" eval="DateTime.now().strftime('%Y-%m-%d 12:00:00')"></field>
|
||||
<field name="tag_ids" eval="[(4, ref('website_event_track.event_track_tag3'))]"/>
|
||||
@@ -572,6 +583,7 @@
|
||||
<field name="color">7</field>
|
||||
<field name="event_id" ref="event.event_7"/>
|
||||
<field name="stage_id" ref="event_track_stage3"/>
|
||||
<field name="wishlisted_by_default" eval="True"/>
|
||||
<field name="location_id" ref="website_event_track.event_track_location_online_2"/>
|
||||
<field name="date" eval="DateTime.now().strftime('%Y-%m-%d 13:30:00')"></field>
|
||||
<field name="tag_ids" eval="[(4, ref('website_event_track.event_track_tag1')), (4, ref('website_event_track.event_track_tag12'))]"/>
|
||||
|
||||
-28
@@ -1,33 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="website_event_track.event_track_stage2" model="event.track.stage">
|
||||
<field name="color">0</field>
|
||||
</record>
|
||||
|
||||
<record id="event_track_tag1" model="event.track.tag">
|
||||
<field name="name">Consumers</field>
|
||||
<field name="color">1</field>
|
||||
</record>
|
||||
<record id="event_track_tag2" model="event.track.tag">
|
||||
<field name="name">Sales</field>
|
||||
<field name="color">2</field>
|
||||
</record>
|
||||
<record id="event_track_tag3" model="event.track.tag">
|
||||
<field name="name">Research</field>
|
||||
<field name="color">3</field>
|
||||
</record>
|
||||
<record id="event_track_tag11" model="event.track.tag">
|
||||
<field name="name">Lightning Talks</field>
|
||||
<field name="color">4</field>
|
||||
</record>
|
||||
<record id="event_track_tag12" model="event.track.tag">
|
||||
<field name="name">Round Table</field>
|
||||
<field name="color">5</field>
|
||||
</record>
|
||||
<record id="event_track_tag13" model="event.track.tag">
|
||||
<field name="name">Keynote</field>
|
||||
<field name="color">6</field>
|
||||
</record>
|
||||
|
||||
<record id="event_track_location5" model="event.track.location">
|
||||
<field name="name">Le Foyer du lac</field>
|
||||
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="event_track_tag_category_1" model="event.track.tag.category">
|
||||
<field name="name">Audience</field>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
<record id="event_track_tag_category_2" model="event.track.tag.category">
|
||||
<field name="name">Format</field>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
|
||||
<record id="event_track_tag1" model="event.track.tag">
|
||||
<field name="name">Consumers</field>
|
||||
<field name="color">1</field>
|
||||
<field name="category_id" ref="event_track_tag_category_1"/>
|
||||
<field name="sequence">1</field>
|
||||
</record>
|
||||
<record id="event_track_tag2" model="event.track.tag">
|
||||
<field name="name">Sales</field>
|
||||
<field name="color">2</field>
|
||||
<field name="category_id" ref="event_track_tag_category_1"/>
|
||||
<field name="sequence">2</field>
|
||||
</record>
|
||||
<record id="event_track_tag3" model="event.track.tag">
|
||||
<field name="name">Research</field>
|
||||
<field name="color">3</field>
|
||||
<field name="category_id" ref="event_track_tag_category_1"/>
|
||||
<field name="sequence">3</field>
|
||||
</record>
|
||||
<record id="event_track_tag11" model="event.track.tag">
|
||||
<field name="name">Lightning Talks</field>
|
||||
<field name="color">4</field>
|
||||
<field name="category_id" ref="event_track_tag_category_2"/>
|
||||
<field name="sequence">11</field>
|
||||
</record>
|
||||
<record id="event_track_tag12" model="event.track.tag">
|
||||
<field name="name">Round Table</field>
|
||||
<field name="color">5</field>
|
||||
<field name="category_id" ref="event_track_tag_category_2"/>
|
||||
<field name="sequence">12</field>
|
||||
</record>
|
||||
<record id="event_track_tag13" model="event.track.tag">
|
||||
<field name="name">Keynote</field>
|
||||
<field name="color">6</field>
|
||||
<field name="category_id" ref="event_track_tag_category_2"/>
|
||||
<field name="sequence">13</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
+2
-2
@@ -3,12 +3,12 @@
|
||||
|
||||
<record id="event_7_track_2_visitor_0" model="event.track.visitor">
|
||||
<field name="track_id" ref="website_event_track.event_7_track_2"/>
|
||||
<field name="visitor_id" ref="website_event_online.website_visitor_event_0"/>
|
||||
<field name="visitor_id" ref="website_event.website_visitor_event_0"/>
|
||||
<field name="is_wishlisted" eval="True"/>
|
||||
</record>
|
||||
<record id="event_7_track_2_visitor_2" model="event.track.visitor">
|
||||
<field name="track_id" ref="website_event_track.event_7_track_2"/>
|
||||
<field name="visitor_id" ref="website_event_online.website_visitor_event_2"/>
|
||||
<field name="visitor_id" ref="website_event.website_visitor_event_2"/>
|
||||
<field name="is_wishlisted" eval="True"/>
|
||||
</record>
|
||||
|
||||
@@ -3,7 +3,15 @@
|
||||
|
||||
from . import event_event
|
||||
from . import event_sponsor
|
||||
from . import event_sponsor_type
|
||||
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
|
||||
|
||||
@@ -5,24 +5,6 @@ from odoo import api, fields, models, _
|
||||
from odoo.addons.http_routing.models.ir_http import slug
|
||||
|
||||
|
||||
class EventType(models.Model):
|
||||
_inherit = 'event.type'
|
||||
|
||||
website_track = fields.Boolean(
|
||||
string='Tracks on Website', compute='_compute_website_menu_data',
|
||||
readonly=False, store=True)
|
||||
website_track_proposal = fields.Boolean(
|
||||
string='Tracks Proposals on Website', compute='_compute_website_menu_data',
|
||||
readonly=False, store=True)
|
||||
|
||||
@api.depends('website_menu')
|
||||
def _compute_website_menu_data(self):
|
||||
""" Simply activate or de-activate all menus at once. """
|
||||
for event_type in self:
|
||||
event_type.website_track = event_type.website_menu
|
||||
event_type.website_track_proposal = event_type.website_menu
|
||||
|
||||
|
||||
class Event(models.Model):
|
||||
_inherit = "event.event"
|
||||
|
||||
@@ -103,53 +85,10 @@ class Event(models.Model):
|
||||
if (not menus_update_by_field) or (event in menus_update_by_field.get('website_track_proposal')):
|
||||
event._update_website_menu_entry('website_track_proposal', 'track_proposal_menu_ids', '_get_track_proposal_menu_entries')
|
||||
|
||||
def _update_website_menu_entry(self, fname_bool, fname_o2m, method_name):
|
||||
""" Generic method to create menu entries based on a flag on event. This
|
||||
method is a bit obscure, but is due to preparation of adding new menus
|
||||
entries and pages for event in a stable version, leading to some constraints
|
||||
while developing.
|
||||
|
||||
:param fname_bool: field name (e.g. website_track)
|
||||
:param fname_o2m: o2m linking towards website.event.menu matching the
|
||||
boolean fields (normally an entry ot website.event.menu with type matching
|
||||
the boolean field name)
|
||||
:param method_name: method returning menu entries information: url, sequence, ...
|
||||
"""
|
||||
self.ensure_one()
|
||||
new_menu = None
|
||||
|
||||
if self[fname_bool] and not self[fname_o2m]:
|
||||
# menus not found but boolean True: get menus to create
|
||||
for sequence, menu_data in enumerate(getattr(self, method_name)()):
|
||||
# some modules have 4 data: name, url, xml_id, menu_type; however we
|
||||
# plan to support sequence in future modules, so this hackish code is
|
||||
# necessary to avoid crashing. Not nice, but stable target = meh.
|
||||
if len(menu_data) == 4:
|
||||
(name, url, xml_id, menu_type) = menu_data
|
||||
menu_sequence = sequence
|
||||
elif len(menu_data) == 5:
|
||||
(name, url, xml_id, menu_sequence, menu_type) = menu_data
|
||||
new_menu = self._create_menu(menu_sequence, name, url, xml_id, menu_type=menu_type)
|
||||
elif not self[fname_bool]:
|
||||
# will cascade delete to the website.event.menu
|
||||
self[fname_o2m].mapped('menu_id').sudo().unlink()
|
||||
|
||||
return new_menu
|
||||
|
||||
def _create_menu(self, sequence, name, url, xml_id, menu_type=False):
|
||||
""" Override menu creation from website_event to link a website.event.menu
|
||||
to the newly create menu (either page and url). """
|
||||
website_menu = super(Event, self)._create_menu(sequence, name, url, xml_id, menu_type=menu_type)
|
||||
if menu_type:
|
||||
self.env['website.event.menu'].create({
|
||||
'menu_id': website_menu.id,
|
||||
'event_id': self.id,
|
||||
'menu_type': menu_type,
|
||||
})
|
||||
return website_menu
|
||||
|
||||
def _get_menu_type_field_matching(self):
|
||||
return {'track_proposal': 'website_track_proposal'}
|
||||
res = super(Event, self)._get_menu_type_field_matching()
|
||||
res['track_proposal'] = 'website_track_proposal'
|
||||
return res
|
||||
|
||||
def _get_track_menu_entries(self):
|
||||
""" Method returning menu entries to display on the website view of the
|
||||
@@ -164,10 +103,11 @@ class Event(models.Model):
|
||||
"""
|
||||
self.ensure_one()
|
||||
return [
|
||||
(_('Talks'), '/event/%s/track' % slug(self), False, 'track'),
|
||||
(_('Agenda'), '/event/%s/agenda' % slug(self), False, False)]
|
||||
(_('Talks'), '/event/%s/track' % slug(self), False, 10, 'track'),
|
||||
(_('Agenda'), '/event/%s/agenda' % slug(self), False, 70, False)
|
||||
]
|
||||
|
||||
def _get_track_proposal_menu_entries(self):
|
||||
""" See website_event_track._get_track_menu_entries() """
|
||||
self.ensure_one()
|
||||
return [(_('Talk Proposals'), '/event/%s/track_proposal' % slug(self), False, 'track_proposal')]
|
||||
return [(_('Talk Proposals'), '/event/%s/track_proposal' % slug(self), False, 15, 'track_proposal')]
|
||||
|
||||
@@ -2,36 +2,40 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
|
||||
class SponsorType(models.Model):
|
||||
_name = "event.sponsor.type"
|
||||
_description = 'Event Sponsor Type'
|
||||
_order = "sequence"
|
||||
|
||||
name = fields.Char('Sponsor Type', required=True, translate=True)
|
||||
sequence = fields.Integer('Sequence')
|
||||
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):
|
||||
@@ -39,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:
|
||||
|
||||
+5
-1
@@ -5,8 +5,12 @@ from odoo import fields, models
|
||||
|
||||
|
||||
class SponsorType(models.Model):
|
||||
_inherit = "event.sponsor.type"
|
||||
_name = "event.sponsor.type"
|
||||
_description = 'Event Sponsor Type'
|
||||
_order = "sequence"
|
||||
|
||||
name = fields.Char('Sponsor Type', required=True, translate=True)
|
||||
sequence = fields.Integer('Sequence')
|
||||
display_ribbon_style = fields.Selection([
|
||||
('no_ribbon', 'No Ribbon'),
|
||||
('Gold', 'Gold'),
|
||||
@@ -2,37 +2,14 @@
|
||||
# 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.tools.translate import _, html_translate
|
||||
from odoo.addons.http_routing.models.ir_http import slug
|
||||
|
||||
|
||||
class TrackTag(models.Model):
|
||||
_name = "event.track.tag"
|
||||
_description = 'Event Track Tag'
|
||||
_order = 'name'
|
||||
|
||||
def _default_color(self):
|
||||
return randint(1, 11)
|
||||
|
||||
name = fields.Char('Tag Name', required=True)
|
||||
track_ids = fields.Many2many('event.track', string='Tracks')
|
||||
color = fields.Integer(
|
||||
string='Color Index', default=lambda self: self._default_color(),
|
||||
help="Note that colorless tags won't be available on the website.")
|
||||
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'unique (name)', "Tag name already exists !"),
|
||||
]
|
||||
|
||||
|
||||
class TrackLocation(models.Model):
|
||||
_name = "event.track.location"
|
||||
_description = 'Event Track Location'
|
||||
|
||||
name = fields.Char('Location', required=True)
|
||||
from odoo.osv import expression
|
||||
from odoo.tools.mail import is_html_empty
|
||||
from odoo.tools.translate import _, html_translate
|
||||
|
||||
|
||||
class Track(models.Model):
|
||||
@@ -45,10 +22,36 @@ class Track(models.Model):
|
||||
def _get_default_stage_id(self):
|
||||
return self.env['event.track.stage'].search([], limit=1).id
|
||||
|
||||
# description
|
||||
name = fields.Char('Title', required=True, translate=True)
|
||||
event_id = fields.Many2one('event.event', 'Event', required=True)
|
||||
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')
|
||||
tag_ids = fields.Many2many('event.track.tag', string='Tags')
|
||||
description = fields.Html(translate=html_translate, sanitize_attributes=False, sanitize_form=False)
|
||||
color = fields.Integer('Color')
|
||||
priority = fields.Selection([
|
||||
('0', 'Low'), ('1', 'Medium'),
|
||||
('2', 'High'), ('3', 'Highest')],
|
||||
'Priority', required=True, default='1')
|
||||
# management
|
||||
stage_id = fields.Many2one(
|
||||
'event.track.stage', string='Stage', ondelete='restrict',
|
||||
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'),
|
||||
('blocked', 'Red')], string='Kanban State',
|
||||
copy=False, default='normal', required=True, tracking=True,
|
||||
help="A track's kanban state indicates special situations affecting it:\n"
|
||||
" * Grey is the default situation\n"
|
||||
" * Red indicates something is preventing the progress of this track\n"
|
||||
" * Green indicates the track is ready to be pulled to the next stage")
|
||||
# speaker
|
||||
partner_id = fields.Many2one('res.partner', 'Speaker')
|
||||
partner_name = fields.Char(
|
||||
string='Name', compute='_compute_partner_info',
|
||||
@@ -59,34 +62,79 @@ 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')
|
||||
tag_ids = fields.Many2many('event.track.tag', string='Tags')
|
||||
stage_id = fields.Many2one(
|
||||
'event.track.stage', string='Stage', ondelete='restrict',
|
||||
index=True, copy=False, default=_get_default_stage_id,
|
||||
group_expand='_read_group_stage_ids',
|
||||
required=True, tracking=True)
|
||||
kanban_state = fields.Selection([
|
||||
('normal', 'Grey'),
|
||||
('done', 'Green'),
|
||||
('blocked', 'Red')], string='Kanban State',
|
||||
copy=False, default='normal', required=True, tracking=True,
|
||||
help="A track's kanban state indicates special situations affecting it:\n"
|
||||
" * Grey is the default situation\n"
|
||||
" * Red indicates something is preventing the progress of this track\n"
|
||||
" * Green indicates the track is ready to be pulled to the next stage")
|
||||
description = fields.Html(translate=html_translate, sanitize_attributes=False, sanitize_form=False)
|
||||
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.")
|
||||
location_id = fields.Many2one('event.track.location', 'Location')
|
||||
event_id = fields.Many2one('event.event', 'Event', required=True)
|
||||
color = fields.Integer('Color')
|
||||
priority = fields.Selection([
|
||||
('0', 'Low'), ('1', 'Medium'),
|
||||
('2', 'High'), ('3', 'Highest')],
|
||||
'Priority', required=True, default='1')
|
||||
image = fields.Image("Image", max_width=128, max_height=128)
|
||||
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(
|
||||
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.""")
|
||||
# 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):
|
||||
@@ -95,6 +143,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:
|
||||
@@ -103,6 +153,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:
|
||||
@@ -112,8 +179,136 @@ 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)]
|
||||
|
||||
# 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:
|
||||
@@ -123,12 +318,18 @@ 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 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'):
|
||||
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']])
|
||||
@@ -139,6 +340,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]
|
||||
@@ -180,6 +389,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'),
|
||||
@@ -189,3 +402,91 @@ class Track(models.Model):
|
||||
'view_id': False,
|
||||
'type': 'ir.actions.act_window',
|
||||
}
|
||||
|
||||
def get_backend_menu_id(self):
|
||||
return self.env.ref('event.event_main_menu').id
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 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
|
||||
|
||||
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]
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class TrackLocation(models.Model):
|
||||
_name = "event.track.location"
|
||||
_description = 'Event Track Location'
|
||||
|
||||
name = fields.Char('Location', required=True)
|
||||
@@ -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')
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from random import randint
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class TrackTag(models.Model):
|
||||
_name = "event.track.tag"
|
||||
_description = 'Event Track Tag'
|
||||
_order = "category_id, sequence, name"
|
||||
|
||||
def _default_color(self):
|
||||
return randint(1, 11)
|
||||
|
||||
name = fields.Char('Tag Name', required=True)
|
||||
track_ids = fields.Many2many('event.track', string='Tracks')
|
||||
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 !"),
|
||||
]
|
||||
-8
@@ -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")
|
||||
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class EventType(models.Model):
|
||||
_inherit = 'event.type'
|
||||
|
||||
website_track = fields.Boolean(
|
||||
string='Tracks on Website', compute='_compute_website_menu_data',
|
||||
readonly=False, store=True)
|
||||
website_track_proposal = fields.Boolean(
|
||||
string='Tracks Proposals on Website', compute='_compute_website_menu_data',
|
||||
readonly=False, store=True)
|
||||
|
||||
@api.depends('website_menu')
|
||||
def _compute_website_menu_data(self):
|
||||
""" Simply activate or de-activate all menus at once. """
|
||||
for event_type in self:
|
||||
event_type.website_track = event_type.website_menu
|
||||
event_type.website_track_proposal = event_type.website_menu
|
||||
@@ -5,12 +5,8 @@ from odoo import fields, models
|
||||
|
||||
|
||||
class EventMenu(models.Model):
|
||||
_name = "website.event.menu"
|
||||
_description = "Website Event Menu"
|
||||
_inherit = "website.event.menu"
|
||||
|
||||
menu_id = fields.Many2one('website.menu', string='Menu', ondelete='cascade')
|
||||
event_id = fields.Many2one('event.event', string='Event', ondelete='cascade')
|
||||
menu_type = fields.Selection([
|
||||
('track', 'Event Tracks Menus'),
|
||||
('track_proposal', 'Event Proposals Menus'),
|
||||
])
|
||||
menu_type = fields.Selection(
|
||||
selection_add=[('track', 'Event Tracks Menus'), ('track_proposal', 'Event Proposals Menus')],
|
||||
ondelete={'track': 'cascade', 'track_proposal': 'cascade'})
|
||||
|
||||
@@ -18,9 +18,6 @@ class WebsiteMenu(models.Model):
|
||||
# agenda page that has also menu_type='track'
|
||||
if event_menu.menu_type == 'track' and '/track' in event_menu.menu_id.url:
|
||||
to_update.append('website_track')
|
||||
for menu_type, fname in event_menu.event_id._get_menu_type_field_matching().items():
|
||||
if event_menu.menu_type == menu_type:
|
||||
to_update.append(fname)
|
||||
|
||||
# call super that resumes the unlink of menus entries (including website event menus)
|
||||
res = super(WebsiteMenu, self).unlink()
|
||||
|
||||
@@ -12,5 +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
|
||||
access_website_event_menu_public,website.event.menu,model_website_event_menu,,1,0,0,0
|
||||
access_website_event_menu_manager,website.event.menu,model_website_event_menu,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
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user