[IMP] website_event_track_online : add wishlist mechanism on event tracks
RATIONALE Events are sometimes held online, gathering a community. In this merge we improve Event application to better support full-online events with improved tracks, wishlists, chat rooms, ... PURPOSE Allow visitors and logged users to wishlist tracks and link tracks to visitors. Purpose is to improve engagement and promote tracks before an event. SPECIFICATIONS: TRACK WISHLIST Add a link between visitors or partners, and tracks. This link is done in a new many2many relationship build on event.track.visitor new model. It is used to store the wishlist flag. That way even public users can wishlist a track using their visitor. At login, information is propagated to their partner. It means that wishlist are saved if customer logs in or registers. We also add a widget for frontend pages. It allows to toggle the wishlist flag on tracks. It is based on visitor and/or current user, leading to a complete flow even for anonymous people. We allow to set some tracks as "keynotes". Those are considered as wishlisted by default. In order to avoid storing too much information, we do not create an entry in the wishlist table for each attendee. We simply consider all event attendees to have wishlisted this track by default. In order to be able to mute a keynote, a blacklist mechanism is implemented. Using the same relationship table between visitors or partners and tracks, a flag is added that blacklists the track. Fields are added on visitor allowing to dynamically build a domain to find visitors that wishlisted a track (or did not blacklist list in case of default wishlist). This could be used for example to automatically contact attendees before a track, like what is done in enterprise counterpart of this merge. LINKS Community PR #53540 Enterprise PR odoo/enterprise#11384 Task ID-2252655 (Main Online Event task) Task ID-2283796 (Event B2Basics / Registration Flow Task ID-2284043 (Visitor-based track wishlist) Task ID-2283869 (Notify attendees by push) Co-Authored-By: Aurélien Warnon <awa@odoo.com> Co-Authored-By: David Beguin <dbe@odoo.com> Co-Authored-By: Thibault Delavallée <tde@odoo.com>
This commit is contained in:
committed by
Thibault Delavallée
co-authored by
Aurélien Warnon
Thibault Delavallée
parent
0659a500ef
commit
8d37126629
@@ -1,4 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import controllers
|
||||
from . import models
|
||||
from . import tests
|
||||
|
||||
@@ -16,8 +16,18 @@
|
||||
'website_event_track',
|
||||
],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'views/assets.xml',
|
||||
'views/event_track_templates_reminder.xml',
|
||||
'views/event_track_templates.xml',
|
||||
'views/event_track_views.xml',
|
||||
'views/event_track_visitor_views.xml',
|
||||
'views/website_visitor_views.xml',
|
||||
'views/event_menus.xml',
|
||||
],
|
||||
'demo': [
|
||||
'data/event_track_demo.xml',
|
||||
'data/event_track_visitor_demo.xml',
|
||||
],
|
||||
'application': False,
|
||||
'installable': True,
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import event_track
|
||||
@@ -0,0 +1,68 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from werkzeug.exceptions import Forbidden, NotFound
|
||||
|
||||
from odoo import exceptions, http
|
||||
from odoo.http import request
|
||||
|
||||
|
||||
class WebsiteEventTrackController(http.Controller):
|
||||
|
||||
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
|
||||
|
||||
@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
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="website_event_track.event_7_track_1" model="event.track">
|
||||
<field name="wishlisted_by_default" eval="True"/>
|
||||
</record>
|
||||
<record id="website_event_track.event_7_track_4" model="event.track">
|
||||
<field name="wishlisted_by_default" eval="True"/>
|
||||
</record>
|
||||
<record id="website_event_track.event_7_track_6" model="event.track">
|
||||
<field name="wishlisted_by_default" eval="True"/>
|
||||
</record>
|
||||
<record id="website_event_track.event_7_track_10" model="event.track">
|
||||
<field name="wishlisted_by_default" eval="True"/>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo><data>
|
||||
|
||||
<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="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="is_wishlisted" eval="True"/>
|
||||
</record>
|
||||
|
||||
</data></odoo>
|
||||
@@ -0,0 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import event_track_visitor
|
||||
from . import event_track
|
||||
from . import website_visitor
|
||||
@@ -0,0 +1,128 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.osv import expression
|
||||
|
||||
|
||||
class Track(models.Model):
|
||||
_name = "event.track"
|
||||
_inherit = ['event.track']
|
||||
|
||||
# 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.""")
|
||||
|
||||
# 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)]
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 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
|
||||
@@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class TrackVisitor(models.Model):
|
||||
""" Table linking track and visitors. """
|
||||
_name = 'event.track.visitor'
|
||||
_description = 'Track / Visitor Link'
|
||||
_table = 'event_track_visitor'
|
||||
_rec_name = 'track_id'
|
||||
_order = 'track_id'
|
||||
|
||||
partner_id = fields.Many2one(
|
||||
'res.partner', string='Partner', compute='_compute_partner_id',
|
||||
index=True, ondelete='set null', readonly=False, store=True)
|
||||
visitor_id = fields.Many2one(
|
||||
'website.visitor', string='Visitor', index=True, ondelete='cascade')
|
||||
track_id = fields.Many2one(
|
||||
'event.track', string='Track',
|
||||
index=True, required=True, ondelete='cascade')
|
||||
is_wishlisted = fields.Boolean(string="Is Wishlisted")
|
||||
is_blacklisted = fields.Boolean(string="Is reminder off", help="As key track cannot be un-wishlisted, this field store the partner choice to remove the reminder for key tracks.")
|
||||
|
||||
@api.depends('visitor_id')
|
||||
def _compute_partner_id(self):
|
||||
for track_visitor in self:
|
||||
if track_visitor.visitor_id.partner_id and not track_visitor.partner_id:
|
||||
track_visitor.partner_id = track_visitor.visitor_id.partner_id
|
||||
elif not track_visitor.partner_id:
|
||||
track_visitor.partner_id = False
|
||||
@@ -0,0 +1,75 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class WebsiteVisitor(models.Model):
|
||||
_name = 'website.visitor'
|
||||
_inherit = ['website.visitor']
|
||||
|
||||
event_track_visitor_ids = fields.One2many(
|
||||
'event.track.visitor', 'visitor_id', string="Track Visitors",
|
||||
groups='event.group_event_user')
|
||||
event_track_wishlisted_ids = fields.Many2many(
|
||||
'event.track', string="Wishlisted Tracks",
|
||||
compute="_compute_event_track_wishlisted_ids", compute_sudo=True,
|
||||
search="_search_event_track_wishlisted_ids",
|
||||
groups="event.group_event_user")
|
||||
event_track_wishlisted_count = fields.Integer(
|
||||
string="# Wishlisted",
|
||||
compute="_compute_event_track_wishlisted_ids", compute_sudo=True,
|
||||
groups='event.group_event_user')
|
||||
|
||||
@api.depends('parent_id', 'event_track_visitor_ids.track_id', 'event_track_visitor_ids.is_wishlisted')
|
||||
def _compute_event_track_wishlisted_ids(self):
|
||||
# include parent's track visitors in a visitor o2m field. We don't add
|
||||
# child one as child should not have track visitors (moved to the parent)
|
||||
all_visitors = self + self.parent_id
|
||||
results = self.env['event.track.visitor'].read_group(
|
||||
[('visitor_id', 'in', all_visitors.ids), ('is_wishlisted', '=', True)],
|
||||
['visitor_id', 'track_id:array_agg'],
|
||||
['visitor_id']
|
||||
)
|
||||
track_ids_map = {result['visitor_id'][0]: result['track_id'] for result in results}
|
||||
for visitor in self:
|
||||
visitor_track_ids = track_ids_map.get(visitor.id, [])
|
||||
parent_track_ids = track_ids_map.get(visitor.parent_id.id, [])
|
||||
visitor.event_track_wishlisted_ids = visitor_track_ids + [track_id for track_id in parent_track_ids if track_id not in visitor_track_ids]
|
||||
visitor.event_track_wishlisted_count = len(visitor.event_track_wishlisted_ids)
|
||||
|
||||
def _search_event_track_wishlisted_ids(self, operator, operand):
|
||||
""" Search visitors with terms on wishlisted tracks. E.g. [('event_track_wishlisted_ids',
|
||||
'in', [1, 2])] should return visitors having wishlisted tracks 1, 2 as
|
||||
well as their children for notification purpose. """
|
||||
if operator == "not in":
|
||||
raise NotImplementedError("Unsupported 'Not In' operation on track wishlist visitors")
|
||||
|
||||
track_visitors = self.env['event.track.visitor'].sudo().search([
|
||||
('track_id', operator, operand),
|
||||
('is_wishlisted', '=', True)
|
||||
])
|
||||
if track_visitors:
|
||||
visitors = track_visitors.visitor_id
|
||||
# search children, even archived one, to contact them
|
||||
children = self.env['website.visitor'].with_context(
|
||||
active_test=False
|
||||
).sudo().search([('parent_id', 'in', visitors.ids)])
|
||||
visitor_ids = (visitors + children).ids
|
||||
else:
|
||||
visitor_ids = []
|
||||
|
||||
return [('id', 'in', visitor_ids)]
|
||||
|
||||
def _link_to_partner(self, partner, update_values=None):
|
||||
""" Propagate partner update to track_visitor records """
|
||||
if partner:
|
||||
track_visitor_wo_partner = self.event_track_visitor_ids.filtered(lambda track_visitor: not track_visitor.partner_id)
|
||||
if track_visitor_wo_partner:
|
||||
track_visitor_wo_partner.partner_id = partner
|
||||
super(WebsiteVisitor, self)._link_to_partner(partner, update_values=update_values)
|
||||
|
||||
def _link_to_visitor(self, target, keep_unique=True):
|
||||
""" Override linking process to link wishlist to the final visitor. """
|
||||
self.event_track_visitor_ids.visitor_id = target.id
|
||||
return super(WebsiteVisitor, self)._link_to_visitor(target, keep_unique=keep_unique)
|
||||
@@ -0,0 +1,3 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
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
|
||||
|
@@ -0,0 +1,86 @@
|
||||
odoo.define('website_event_track.website_event_track_reminder', function (require) {
|
||||
'use strict';
|
||||
|
||||
var core = require('web.core');
|
||||
var _t = core._t;
|
||||
var utils = require('web.utils');
|
||||
var publicWidget = require('web.public.widget');
|
||||
|
||||
publicWidget.registry.websiteEventTrackReminder = publicWidget.Widget.extend({
|
||||
selector: '.o_wetrack_js_reminder',
|
||||
events: {
|
||||
'click': '_onReminderToggleClick',
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this._onReminderToggleClick = _.debounce(this._onReminderToggleClick, 500, true);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onReminderToggleClick: function (ev) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
var self = this;
|
||||
var $trackLink = $(ev.currentTarget).find('i');
|
||||
|
||||
if (this.reminderOn === undefined) {
|
||||
this.reminderOn = $trackLink.data('reminderOn');
|
||||
}
|
||||
|
||||
this._rpc({
|
||||
route: '/event/track/toggle_reminder',
|
||||
params: {
|
||||
track_id: $trackLink.data('trackId'),
|
||||
set_reminder_on: !this.reminderOn
|
||||
},
|
||||
}).then(function (result) {
|
||||
if (result.error && result.error === 'ignored') {
|
||||
self.displayNotification({
|
||||
type: 'info',
|
||||
title: _t('Error'),
|
||||
message: _.str.sprintf(_t('Talk already in your Favorites')),
|
||||
});
|
||||
} else {
|
||||
self.reminderOn = result.reminderOn;
|
||||
var reminderText = self.reminderOn ? _t('Reminder On') : _t('Set Reminder');
|
||||
self.$('.o_wetrack_js_reminder_text').text(reminderText);
|
||||
self._updateDisplay();
|
||||
var message = self.reminderOn ? _t('Talk added to your Favorites') : _t('Talk removed from your Favorites');
|
||||
self.displayNotification({
|
||||
type: 'info',
|
||||
title: message
|
||||
});
|
||||
}
|
||||
if (result.visitor_uuid) {
|
||||
utils.set_cookie('visitor_uuid', result.visitor_uuid);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_updateDisplay: function () {
|
||||
var $trackLink = this.$el.find('i');
|
||||
if (this.reminderOn) {
|
||||
$trackLink.addClass('fa-bell').removeClass('fa-bell-o');
|
||||
$trackLink.attr('title', _t('Reminder On'));
|
||||
} else {
|
||||
$trackLink.addClass('fa-bell-o').removeClass('fa-bell');
|
||||
$trackLink.attr('title', _t('Set Reminder'));
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
return publicWidget.registry.websiteEventTrackReminder;
|
||||
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* EVENT TOOL: REMINDER WIDGET
|
||||
*/
|
||||
.o_wevent_event .o_wetrack_js_reminder {
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
cursor: pointer;
|
||||
background: grey;
|
||||
|
||||
i {
|
||||
&.fa-bell {
|
||||
color: gold;
|
||||
}
|
||||
&.fa-bell-o {
|
||||
color: white
|
||||
}
|
||||
}
|
||||
|
||||
// Icon only
|
||||
&.o_wetrack_reminder_light {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
i {
|
||||
&.inactive_color_dark {
|
||||
color: grey;
|
||||
}
|
||||
}
|
||||
&:not(.o_wetrack_reminder_small) {
|
||||
i {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Text included
|
||||
&:not(.o_wetrack_reminder_light) {
|
||||
&.o_wetrack_reminder_small {
|
||||
padding: 0.25rem 0.50rem;
|
||||
span {
|
||||
min-width: 70px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<template id="assets_frontend" inherit_id="website.assets_frontend" name="Event Track Online Assets Frontend">
|
||||
<xpath expr="//link[last()]" position="after">
|
||||
<link rel="stylesheet" type="text/scss" href="/website_event_track_online/static/src/scss/event_track_templates.scss"/>
|
||||
</xpath>
|
||||
<xpath expr="//script[last()]" position="after">
|
||||
<script type="text/javascript" src="/website_event_track_online/static/src/js/event_track_reminder.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo><data>
|
||||
|
||||
<menuitem id="event_track_visitor_menu"
|
||||
name="Track Visitors"
|
||||
sequence="38"
|
||||
action="event_track_visitor_action"
|
||||
parent="event.menu_event_configuration"
|
||||
groups="base.group_no_one"/>
|
||||
|
||||
</data></odoo>
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<template id="tracks_wishlist"
|
||||
name="Wishlist on Tracks"
|
||||
inherit_id="website_event_track.tracks"
|
||||
customize_show="True">
|
||||
<xpath expr="//h3" position="replace">
|
||||
<h3 class="mt0 mb0 d-flex">
|
||||
<a t-attf-href="/event/#{ slug(event) }/track/#{ slug(track) }" class="text-secondary"><span t-field="track.name"> </span></a>
|
||||
<small t-if="not track.website_published" class="badge badge-danger">unpublished</small>
|
||||
<t t-call="website_event_track_online.track_widget_reminder"/>
|
||||
</h3>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="track_view_wishlist"
|
||||
name="Wishlist on Track"
|
||||
inherit_id="website_event_track.track_view"
|
||||
customize_show="True">
|
||||
<xpath expr="//h1" position="replace">
|
||||
<div class="d-flex">
|
||||
<h1 t-field="track.name"/>
|
||||
<div class="ml-2 pt-1">
|
||||
<t t-call="website_event_track_online.track_widget_reminder"/>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- Options
|
||||
* reminder_light: no text displayed
|
||||
* reminder_small: text displayed as small
|
||||
* light_theme: bell is white/gold if set; otherwse bell is gray / white
|
||||
-->
|
||||
<template id="track_widget_reminder">
|
||||
<div t-att-class="'o_wetrack_js_reminder btn d-flex align-items-center justify-content-between rounded %s %s' % (
|
||||
'o_wetrack_reminder_light' if reminder_light else '',
|
||||
'o_wetrack_reminder_small' if reminder_small else '')">
|
||||
<span t-if="not reminder_light"
|
||||
t-att-class="'o_wetrack_js_reminder_text text-white text-left mr-1 %s' % ('small d-sm-inline' if reminder_small else '')">
|
||||
<t t-esc="'Set Reminder' if not track.is_reminder_on else 'Reminder On'"/>
|
||||
</span>
|
||||
<i t-att-class="'fa fa-bell%s inactive_color_%s' % ('' if track.is_reminder_on else '-o', 'dark' if reminder_light and not light_theme else 'light')"
|
||||
t-att-data-track-id="track.id"
|
||||
t-att-title="'Reminder On' if track.is_reminder_on else 'Set a Reminder'"
|
||||
t-att-data-reminder-on="bool(track.is_reminder_on)"></i>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
<record id="website_visitor_action_from_track" model="ir.actions.act_window">
|
||||
<field name="name">Visitors Wishlist</field>
|
||||
<field name="res_model">website.visitor</field>
|
||||
<field name="view_mode">kanban,tree,form,graph</field>
|
||||
<field name="domain">[('event_track_wishlisted_ids', 'in', [active_id])]</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Wait for visitors to wishlist your tracks
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="event_track_view_search" model="ir.ui.view">
|
||||
<field name="name">event.track.view.search.inherit.event.track.online</field>
|
||||
<field name="model">event.track</field>
|
||||
<field name="inherit_id" ref="website_event_track.view_event_track_search"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//filter[@name='message_needaction']" position="after">
|
||||
<filter string="Always Wishlisted" name="filter_wishlisted_by_default" domain="[('wishlisted_by_default', '=', True)]"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="event_track_view_tree" model="ir.ui.view" >
|
||||
<field name="name">event.track.view.tree.inherit.event.track.online</field>
|
||||
<field name="model">event.track</field>
|
||||
<field name="inherit_id" ref="website_event_track.view_event_track_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='event_id']" position="after">
|
||||
<field name="wishlisted_by_default" optional="hide"/>
|
||||
<field name="wishlist_visitor_count" optional="hide"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="event_track_view_form" model="ir.ui.view" >
|
||||
<field name="name">event.track.view.form.inherit.event.track.online</field>
|
||||
<field name="model">event.track</field>
|
||||
<field name="inherit_id" ref="website_event_track.view_event_track_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="active" position='after'>
|
||||
<field name="wishlisted_by_default"/>
|
||||
</field>
|
||||
<field name="is_published" position="before">
|
||||
<button name="%(website_event_track_online.website_visitor_action_from_track)d"
|
||||
type="action"
|
||||
class="oe_stat_button"
|
||||
icon="fa-bell">
|
||||
<field name="wishlist_visitor_count" string="Wishlisted By" widget="statinfo"/>
|
||||
</button>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="event_track_action_from_visitor" model="ir.actions.act_window">
|
||||
<field name="name">Wishlisted Tracks</field>
|
||||
<field name="res_model">event.track</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="domain">[('wishlist_visitor_ids', 'in', [active_id])]</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_empty_folder">
|
||||
No track wishlisted by this visitor
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo><data>
|
||||
|
||||
<record id="event_track_visitor_view_search" model="ir.ui.view" >
|
||||
<field name="name">event.track.visitor.view.search</field>
|
||||
<field name="model">event.track.visitor</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Track Visitors">
|
||||
<field name="track_id"/>
|
||||
<field name="visitor_id"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="is_wishlisted"/>
|
||||
<group string="Group By" expand="0">
|
||||
<filter string="Track" name="groupby_track_id" context="{'group_by': 'track_id'}"/>
|
||||
<filter string="Visitor" name="groupby_visitor_id" context="{'group_by': 'visitor_id'}"/>
|
||||
<filter string="Customer" name="groupby_partner_id" context="{'group_by': 'partner_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="event_track_visitor_view_form" model="ir.ui.view">
|
||||
<field name="name">event.track.visitor.view.form</field>
|
||||
<field name="model">event.track.visitor</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Track Visitor">
|
||||
<sheet string="Track Visitor">
|
||||
<group>
|
||||
<group>
|
||||
<field name="track_id"/>
|
||||
<field name="is_wishlisted"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="visitor_id"/>
|
||||
<field name="partner_id"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="event_track_visitor_view_list" model="ir.ui.view">
|
||||
<field name="name">event.track.visitor.view.list</field>
|
||||
<field name="model">event.track.visitor</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Track Visitors">
|
||||
<field name="track_id"/>
|
||||
<field name="visitor_id"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="is_wishlisted"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="event_track_visitor_action" model="ir.actions.act_window">
|
||||
<field name="name">Track Visitors</field>
|
||||
<field name="res_model">event.track.visitor</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Add a new track visitor
|
||||
</p><p>
|
||||
Track Visitors store statistics on track attendance, including
|
||||
wishlist management.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data></odoo>
|
||||
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo><data>
|
||||
|
||||
<record id="website_visitor_view_tree" model="ir.ui.view">
|
||||
<field name="name">website.visitor.view.tree.inherit.event.track</field>
|
||||
<field name="model">website.visitor</field>
|
||||
<field name="inherit_id" ref="website_event_online.website_visitor_view_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='event_registration_count']" position="after">
|
||||
<field name="event_track_wishlisted_count"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="website_visitor_view_form" model="ir.ui.view">
|
||||
<field name="name">website.visitor.view.form.inherit.event.track</field>
|
||||
<field name="model">website.visitor</field>
|
||||
<field name="inherit_id" ref="website_event_online.website_visitor_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//button[@id='w_visitor_visit_counter']" position="before">
|
||||
<button name="%(website_event_track_online.event_track_action_from_visitor)d"
|
||||
type="action"
|
||||
class="oe_stat_button" icon="fa-ticket"
|
||||
attrs="{'invisible': [('event_track_wishlisted_count', '=', 0)]}">
|
||||
<field name="event_track_wishlisted_count" widget="statinfo" string="Tracks"/>
|
||||
</button>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="website_visitor_view_kanban" model="ir.ui.view">
|
||||
<field name="name">website.visitor.view.kanban.inherit.event.track</field>
|
||||
<field name="model">website.visitor</field>
|
||||
<field name="inherit_id" ref="website_event_online.website_visitor_view_kanban"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="page_ids" position="after">
|
||||
<field name="event_track_wishlisted_count"/>
|
||||
</field>
|
||||
<xpath expr="//div[@id='o_page_count']" position="after">
|
||||
<div>Wishlisted Tracks<span class="float-right font-weight-bold"><field name="event_track_wishlisted_count"/></span></div>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('w_visitor_kanban_actions_ungrouped')]" position="before">
|
||||
<div class="col">
|
||||
<b><field name="event_track_wishlisted_count"/></b>
|
||||
<div>Wishlisted Tracks</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data></odoo>
|
||||
Reference in New Issue
Block a user