[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:
David Beguin
2020-08-04 14:28:29 +00:00
committed by Thibault Delavallée
co-authored by Aurélien Warnon Thibault Delavallée
parent 0659a500ef
commit 8d37126629
20 changed files with 757 additions and 0 deletions
@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 event_track_visitor_access_all event.track.visitor.access.all model_event_track_visitor 0 0 0 0
3 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>