[IMP] event: change event form view to improve usability

PURPOSE

Change the event form view to improve its usability and make it clearer

SPECIFICATION

- Change some stats button icons
- Change order of the fields
- Put the date fields just below the title since it's a mandatory field.
- Convert the note field into an html field.
- Convert seats_availability selection field into seats_limited a boolean field.
- Move 'website_menu', 'website_track' and website_track_proposal' to the customize dropdown menu in the frontend.

LINKS

Task ID : 2198660
Enterprise PR : odoo/enterprise#8954

--
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr

closes odoo/odoo#46659

Related: odoo/upgrade#1002
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
Odoo's Mergebot
2020-03-31 09:49:27 +02:00
committed by GitHub
29 changed files with 375 additions and 242 deletions
-1
View File
@@ -17,7 +17,6 @@
<!-- Event Categories -->
<record id="event_type_data_online" model="event.type">
<field name="name">Online</field>
<field name="is_online" eval="True"/>
<field name="auto_confirm" eval="True"/>
<field name="use_mail_schedule" eval="False"/>
</record>
+3 -3
View File
@@ -75,7 +75,7 @@
<field name="user_id" ref="base.user_demo"/>
<field name="date_begin" eval="(DateTime.now() + timedelta(days=10)).strftime('%Y-%m-%d 08:00:00')"/>
<field name="date_end" eval="(DateTime.now() + timedelta(days=14)).strftime('%Y-%m-%d 18:00:00')"/>
<field name="seats_availability">limited</field>
<field name="seats_limited">True</field>
<field name="seats_max">50</field>
<field name="address_id" ref="event.res_partner_location_2"/>
<field name="date_tz">US/Pacific</field>
@@ -168,7 +168,7 @@
<field eval="(DateTime.today()+ timedelta(days=5)).strftime('%Y-%m-%d 16:30:00')" name="date_end"/>
<field name="event_type_id" ref="event_type_data_online"/>
<field name="address_id" ref="event.res_partner_location_2"/>
<field name="seats_availability">limited</field>
<field name="seats_limited">True</field>
<field name="seats_max">200</field>
<field name="stage_id" ref="event_stage_booked"/>
<field name="tag_ids" eval="[(6, 0, [event_tag_category_1_tag_4,event_tag_category_2_tag_1])]"/>
@@ -241,7 +241,7 @@
<field name="user_id" ref="base.user_admin"/>
<field name="date_begin" eval="(DateTime.today() - timedelta(days=5)).strftime('%Y-%m-%d 18:00:00')"/>
<field name="date_end" eval="(DateTime.today() - timedelta(days=5)).strftime('%Y-%m-%d 22:30:00')"/>
<field name="seats_availability">limited</field>
<field name="seats_limited">True</field>
<field name="seats_max">4</field>
<field name="address_id" ref="event.res_partner_location_2"/>
<field name="date_tz">US/Pacific</field>
+19 -38
View File
@@ -44,8 +44,6 @@ class EventType(models.Model):
help="Events and registrations will automatically be confirmed "
"upon creation, easing the flow for simple events.")
# location
is_online = fields.Boolean(
'Online Event', help='Online events like webinars do not require a specific location and are hosted online.')
use_timezone = fields.Boolean('Use Default Timezone')
default_timezone = fields.Selection(
_tz_get, string='Timezone', default=lambda self: self.env.user.tz or 'UTC')
@@ -141,10 +139,8 @@ class EventEvent(models.Model):
string='Maximum Attendees Number',
compute='_compute_from_event_type', copy=True, readonly=False, store=True,
help="For each event you can define a maximum registration of seats(number of attendees), above this numbers the registrations are not accepted.")
seats_availability = fields.Selection(
[('unlimited', 'Unlimited'), ('limited', 'Limited')],
string='Maximum Attendees', required=True,
compute='_compute_seats_availability', copy=True, readonly=False, store=True)
seats_limited = fields.Boolean('Maximum Attendees', required=True, compute='_compute_seats_limited',
copy=True, readonly=False, store=True)
seats_reserved = fields.Integer(
string='Reserved Seats',
store=True, readonly=True, compute='_compute_seats')
@@ -162,8 +158,9 @@ class EventEvent(models.Model):
compute_sudo=True, readonly=True, compute='_compute_seats')
# Registration fields
auto_confirm = fields.Boolean(
string='Autoconfirm Registrations',
compute='_compute_from_event_type', copy=True, readonly=False, store=True)
string='Autoconfirmation',
compute='_compute_from_event_type', copy=True, readonly=False, store=True,
help = 'Autoconfirm Registrations. Registrations will automatically be confirmed upon creation.')
registration_ids = fields.One2many('event.registration', 'event_id', string='Attendees')
event_registrations_open = fields.Boolean('Registration open', compute='_compute_event_registrations_open')
event_ticket_ids = fields.One2many(
@@ -181,13 +178,9 @@ class EventEvent(models.Model):
is_one_day = fields.Boolean(compute='_compute_field_is_one_day')
start_sale_date = fields.Date('Start sale date', compute='_compute_start_sale_date')
# Location and communication
is_online = fields.Boolean(
string='Online Event', compute='_compute_from_event_type',
copy=True, readonly=False, store=True)
address_id = fields.Many2one(
'res.partner', string='Venue', compute='_compute_address_id',
copy=True, readonly=False, store=True, tracking=True,
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
'res.partner', string='Venue', default=lambda self: self.env.company.partner_id.id,
tracking=True, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
country_id = fields.Many2one(
'res.country', 'Country', related='address_id.country_id',
copy=True, readonly=False, store=True)
@@ -232,11 +225,11 @@ class EventEvent(models.Model):
seats_expected = event.seats_unconfirmed + event.seats_reserved + event.seats_used
event.seats_expected = seats_expected
@api.depends('date_end', 'seats_available', 'seats_availability', 'event_ticket_ids.sale_available')
@api.depends('date_end', 'seats_available', 'seats_limited', 'event_ticket_ids.sale_available')
def _compute_event_registrations_open(self):
for event in self:
event.event_registrations_open = event.date_end and (event.date_end > fields.Datetime.now()) and \
(event.seats_available or event.seats_availability == 'unlimited') and \
(event.seats_available or not event.seats_limited) and \
(not event.event_ticket_ids or any(ticket.sale_available for ticket in event.event_ticket_ids))
@api.depends('stage_id', 'kanban_state')
@@ -302,14 +295,6 @@ class EventEvent(models.Model):
start_dates = [ticket.start_sale_date for ticket in event.event_ticket_ids if ticket.start_sale_date]
event.start_sale_date = min(start_dates) if start_dates else False
@api.depends('is_online')
def _compute_address_id(self):
for event in self:
if event.is_online:
event.address_id = False
elif not event.address_id:
event.address_id = self.env.company.partner_id.id
@api.depends('event_type_id')
def _compute_date_tz(self):
for event in self:
@@ -319,7 +304,7 @@ class EventEvent(models.Model):
event.date_tz = self.env.user.tz or 'UTC'
@api.depends('event_type_id')
def _compute_seats_availability(self):
def _compute_seats_limited(self):
""" Make it separate from ``_compute_from_event_type`` because otherwise
a value given at create (see create override) would protect all other fields
depending on event type id from being computed as compute method will be
@@ -327,9 +312,9 @@ class EventEvent(models.Model):
to compute protected field from re-computation) """
for event in self:
if event.event_type_id.seats_max:
event.seats_availability = 'limited'
if not event.seats_availability:
event.seats_availability = 'unlimited'
event.seats_limited = True
if not event.seats_limited:
event.seats_limited = False
@api.depends('event_type_id')
def _compute_from_event_type(self):
@@ -341,7 +326,6 @@ class EventEvent(models.Model):
Updated by this method
* seats_max -> triggers _compute_seats (all seats computation)
* auto_confirm
* is_online -> triggers _compute_address_id (address_id computation)
* event_mail_ids
* event_ticket_ids -> triggers _compute_start_sale_date (start_sale_date computation)
"""
@@ -349,8 +333,6 @@ class EventEvent(models.Model):
if not event.event_type_id:
if not event.seats_max:
event.seats_max = 0
if not event.is_online:
event.is_online = False
if not event.event_ticket_ids:
event.event_ticket_ids = False
continue
@@ -361,8 +343,6 @@ class EventEvent(models.Model):
if event.event_type_id.auto_confirm:
event.auto_confirm = event.event_type_id.auto_confirm
event.is_online = event.event_type_id.is_online
# compute mailing information (force only if activated and mailing defined)
if event.event_type_id.use_mail_schedule and event.event_type_id.event_type_mail_ids:
event.event_mail_ids = [(5, 0, 0)] + [
@@ -384,9 +364,9 @@ class EventEvent(models.Model):
if event.event_type_id.tag_ids:
event.tag_ids = event.event_type_id.tag_ids
@api.constrains('seats_max', 'seats_available', 'seats_availability')
@api.constrains('seats_max', 'seats_available', 'seats_limited')
def _check_seats_limit(self):
if any(event.seats_availability == 'limited' and event.seats_max and event.seats_available < 0 for event in self):
if any(event.seats_limited and event.seats_max and event.seats_available < 0 for event in self):
raise ValidationError(_('No more available seats.'))
@api.constrains('date_begin', 'date_end')
@@ -412,7 +392,7 @@ class EventEvent(models.Model):
@api.model
def create(self, vals):
# Temporary fix for ``seats_availability`` and ``date_tz`` required fields (see ``_compute_from_event_type``
# Temporary fix for ``seats_limited`` and ``date_tz`` required fields (see ``_compute_from_event_type``
vals.update(self._sync_required_computed(vals))
res = super(EventEvent, self).create(vals)
@@ -433,9 +413,10 @@ class EventEvent(models.Model):
return super(EventEvent, self).copy(default)
def _sync_required_computed(self, values):
# TODO: See if the change to seats_limited affects this ?
""" Call compute fields in cache to find missing values for required fields
(seats_availability and date_tz) in case they are not given in values """
missing_fields = list(set(['seats_availability', 'date_tz']).difference(set(values.keys())))
(seats_limited and date_tz) in case they are not given in values """
missing_fields = list(set(['seats_limited', 'date_tz']).difference(set(values.keys())))
if missing_fields and values:
cache_event = self.new(values)
cache_event._compute_from_event_type()
+2 -2
View File
@@ -92,7 +92,7 @@ class EventRegistration(models.Model):
@api.constrains('event_id', 'state')
def _check_seats_limit(self):
for registration in self:
if registration.event_id.seats_availability == 'limited' and registration.event_id.seats_max and registration.event_id.seats_available < (1 if registration.state == 'draft' else 0):
if registration.event_id.seats_limited and registration.event_id.seats_max and registration.event_id.seats_available < (1 if registration.state == 'draft' else 0):
raise ValidationError(_('No more seats available for this event.'))
@api.constrains('event_ticket_id', 'state')
@@ -151,7 +151,7 @@ class EventRegistration(models.Model):
def _check_auto_confirmation(self):
if any(not registration.event_id.auto_confirm or
(not registration.event_id.seats_available and registration.event_id.seats_availability == 'limited') for registration in self):
(not registration.event_id.seats_available and registration.event_id.seats_limited) for registration in self):
return False
return True
+4 -5
View File
@@ -19,18 +19,17 @@ class EventTemplateTicket(models.Model):
event_type_id = fields.Many2one(
'event.type', string='Event Category', ondelete='cascade', required=True)
# seats
seats_availability = fields.Selection([
('limited', 'Limited'), ('unlimited', 'Unlimited')], string='Seats Limit',
readonly=True, store=True, compute='_compute_seats_availability')
seats_limited = fields.Boolean(string='Seats Limit', readonly=True, store=True,
compute='_compute_seats_limited')
seats_max = fields.Integer(
string='Maximum Seats',
help="Define the number of available tickets. If you have too many registrations you will "
"not be able to sell tickets anymore. Set 0 to ignore this rule set as unlimited.")
@api.depends('seats_max')
def _compute_seats_availability(self):
def _compute_seats_limited(self):
for ticket in self:
ticket.seats_availability = 'limited' if ticket.seats_max else 'unlimited'
ticket.seats_limited = ticket.seats_max
@api.model
def _get_event_ticket_fields_whitelist(self):
-1
View File
@@ -51,7 +51,6 @@ class TestEventCommon(common.SavepointCase):
cls.event_type_complex = cls.env['event.type'].create({
'name': 'Update Type',
'auto_confirm': True,
'is_online': False,
'has_seats_limitation': True,
'seats_max': 30,
'use_timezone': True,
+2 -2
View File
@@ -82,7 +82,7 @@ class TestEventFlow(TestEventCommon):
'date_begin': datetime.datetime.now() + relativedelta(days=-1),
'date_end': datetime.datetime.now() + relativedelta(days=1),
'seats_max': 2,
'seats_availability': 'limited',
'seats_limited': True,
})
self.assertTrue(test_event.auto_confirm)
@@ -124,7 +124,7 @@ class TestEventFlow(TestEventCommon):
'name': 'TestEvent',
'date_begin': datetime.datetime.now() + relativedelta(days=-1),
'date_end': datetime.datetime.now() + relativedelta(days=1),
'seats_availability': 'limited',
'seats_limited': True,
'seats_max': 10,
})
self.assertFalse(test_event.auto_confirm)
+6 -15
View File
@@ -59,21 +59,15 @@ class TestEventData(TestEventCommon):
'date_end': FieldsDatetime.to_string(datetime.today() + timedelta(days=15)),
})
self.assertFalse(event.is_online)
self.assertEqual(event.address_id, self.env.user.company_id.partner_id)
# seats: coming from event type configuration
self.assertEqual(event.seats_availability, 'limited')
self.assertTrue(event.seats_limited)
self.assertEqual(event.seats_available, event.event_type_id.seats_max)
self.assertEqual(event.seats_unconfirmed, 0)
self.assertEqual(event.seats_reserved, 0)
self.assertEqual(event.seats_used, 0)
self.assertEqual(event.seats_expected, 0)
# set is_online: should reset the address_id field
event.update({'is_online': True})
self.assertTrue(event.is_online)
self.assertFalse(event.address_id)
# create registration in order to check the seats computation
self.assertTrue(event.auto_confirm)
for x in range(5):
@@ -117,12 +111,10 @@ class TestEventData(TestEventCommon):
'name': 'Event Update Type',
'date_begin': FieldsDatetime.to_string(datetime.today() + timedelta(days=1)),
'date_end': FieldsDatetime.to_string(datetime.today() + timedelta(days=15)),
'is_online': True,
})
self.assertEqual(event.date_tz, self.env.user.tz)
self.assertEqual(event.seats_availability, 'unlimited')
self.assertFalse(event.seats_limited)
self.assertFalse(event.auto_confirm)
self.assertTrue(event.is_online)
self.assertEqual(event.event_mail_ids, self.env['event.mail'])
event_type.write({
@@ -133,10 +125,9 @@ class TestEventData(TestEventCommon):
})
event.write({'event_type_id': event_type.id})
self.assertEqual(event.date_tz, 'Europe/Paris')
self.assertEqual(event.seats_availability, 'limited')
self.assertTrue(event.seats_limited)
self.assertEqual(event.seats_max, event_type.seats_max)
self.assertTrue(event.auto_confirm)
self.assertFalse(event.is_online)
self.assertEqual(event.event_mail_ids.interval_nbr, 1)
self.assertEqual(event.event_mail_ids.interval_unit, 'days')
self.assertEqual(event.event_mail_ids.interval_type, 'before_event')
@@ -171,7 +162,7 @@ class TestEventData(TestEventCommon):
event.write({
'date_end': datetime.now() + timedelta(days=3),
'seats_max': 1,
'seats_availability': 'limited',
'seats_limited': True,
})
self.assertEqual(event.seats_available, 0)
self.assertFalse(event.event_registrations_open)
@@ -255,11 +246,11 @@ class TestEventTicketData(TestEventCommon):
first_ticket = event.event_ticket_ids.filtered(lambda t: t.name == 'First Ticket')
second_ticket = event.event_ticket_ids.filtered(lambda t: t.name == 'Second Ticket')
self.assertEqual(first_ticket.seats_availability, 'limited')
self.assertTrue(first_ticket.seats_limited)
self.assertTrue(first_ticket.sale_available)
self.assertFalse(first_ticket.is_expired)
self.assertEqual(second_ticket.seats_availability, 'unlimited')
self.assertFalse(second_ticket.seats_limited)
self.assertTrue(second_ticket.sale_available)
self.assertFalse(second_ticket.is_expired)
# sale is ended
+1 -1
View File
@@ -26,7 +26,7 @@ class TestEventSecurity(TestEventCommon):
'name': 'TestEvent',
'date_begin': datetime.now() + relativedelta(days=-1),
'date_end': datetime.now() + relativedelta(days=1),
'seats_availability': 'limited',
'seats_limited': True,
'seats_max': 10,
})
+3 -3
View File
@@ -11,7 +11,7 @@
<field name="name"/>
<field name="description"/>
<field name="seats_max"/>
<field name="seats_availability"/>
<field name="seats_limited"/>
</tree>
</field>
</record>
@@ -26,7 +26,7 @@
<group>
<field name="name"/>
<field name="description"/>
<field name="seats_availability"/>
<field name="seats_limited"/>
<field name="seats_max"/>
</group>
</sheet>
@@ -162,7 +162,7 @@
<group>
<group>
<field name="event_id"/>
<field name="seats_availability"/>
<field name="seats_limited"/>
<field name="seats_available"/>
<field name="start_sale_date"/>
<field name="end_sale_date"/>
+15 -46
View File
@@ -15,20 +15,6 @@
</div>
<h2>Location</h2>
<div class="row mt16 o_settings_container" name="event_type_location">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="is_online"/>
</div>
<div class="o_setting_right_pane">
<label for="is_online"/>
<div class="row">
<div class="col-lg-8 mt16 text-muted">
Online events like webinars do not require a specific location
and are hosted online.
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="use_timezone"/>
@@ -148,7 +134,6 @@
<field name="arch" type="xml">
<search string="Event Templates">
<field name="name"/>
<filter string="Online Event" name="online_event" help="Online Event" domain="[('is_online', '=', True)]"/>
</search>
</field>
</record>
@@ -215,26 +200,10 @@
<div class="oe_title">
<label for="name" class="oe_edit_only" string="Event Name"/>
<h1><field name="name" placeholder="e.g. Conference for Architects"/></h1>
<field name="is_online" groups="base.group_no_one"/>
<label for="is_online" string="Online" groups="base.group_no_one"/>
</div>
<group>
<group>
<field name="active" invisible="1"/>
<field name="organizer_id"/>
<field name="address_id"
attrs="{'invisible': [('is_online', '=', True)]}"
context="{'show_address': 1}"
options='{"always_reload": True}'/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="user_id" domain="[('share', '=', False)]"/>
</group>
<group>
<field name="event_type_id" options="{'no_create':True}"/>
<field name="tag_ids" widget="many2many_tags" options="{'no_quick_create': True}"/>
<field name="auto_confirm" groups="base.group_no_one"/>
</group>
<group>
<label for="date_begin" string="Date"/>
<div class="o_row">
<field name="date_begin" widget="daterange" nolabel="1" class="oe_inline" options="{'related_end_date': 'date_end'}"/>
@@ -242,20 +211,23 @@
<field name="date_end" widget="daterange" nolabel="1" class="oe_inline" options="{'related_start_date': 'date_begin'}"/>
</div>
<field name="date_tz"/>
<field name="organizer_id"/>
<field name="user_id" domain="[('share', '=', False)]"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="event_type_id" string="Template" options="{'no_create':True}"/>
<field name="tag_ids" widget="many2many_tags" options="{'no_quick_create': True}"/>
</group>
<group>
<label for="seats_availability"/>
<field name="address_id"
context="{'show_address': 1}"
options='{"always_reload": True}'/>
<field name="auto_confirm"/>
<label for="seats_limited" string="Limit Registrations"/>
<div>
<field name="seats_availability" widget='radio'/>
<span attrs="{'invisible': [('seats_availability', '=', 'unlimited')]}" class="oe_read_only">
to
</span>
<field name="seats_max" attrs="{'invisible': [('seats_availability', '=', 'unlimited')], 'required': [('seats_availability', '=', 'limited')]}"/>
<field name="seats_limited"/>
<span attrs="{'invisible': [('seats_limited', '=', False)], 'required': [('seats_limited', '=', False)]}">to <field name="seats_max" class="oe_inline"/> Attendees</span>
</div>
</group>
<group class="w-100">
<field name="note" placeholder="Add a note..." nolabel="1"/>
</group>
</group>
<notebook>
<page string="Tickets" name="tickets">
@@ -278,10 +250,8 @@
</tree>
</field>
</page>
<page string="Description" name="event_description">
<group>
<field name="description"/>
</group>
<page string="Notes" name="event_notes">
<field name="note" placeholder="Add a note..."/>
</page>
</notebook>
</sheet>
@@ -436,7 +406,6 @@
<separator/>
<filter string="Upcoming/Running" name="upcoming"
domain="[('date_end', '&gt;=', datetime.datetime.combine(context_today(), datetime.time(0,0,0)))]" help="Upcoming events from today" />
<filter string="Online Events" name="online_events" help="Online Events" domain="[('is_online', '=', True)]"/>
<separator/>
<filter string="Start Date" name="start_date" date="date_begin"/>
<separator/>
@@ -561,7 +530,7 @@
<field name="event_ticket_id"
domain="[
('event_id', '=', event_id),
'|', ('seats_availability', '=', 'unlimited'), ('seats_available', '>', 0)
'|', ('seats_limited', '=', False), ('seats_available', '>', 0)
]"
attrs="{'invisible': [('event_id', '=', False)]}"
options="{'no_open': True, 'no_create': True}"/>
@@ -43,7 +43,7 @@ class TestEventData(TestEventSaleCommon):
# synchronize event
event.write({'event_type_id': event_type.id})
self.assertEqual(event.event_ticket_ids.name, event.event_type_id.event_type_ticket_ids.name)
self.assertEqual(event.event_ticket_ids.seats_availability, 'limited')
self.assertTrue(event.event_ticket_ids.seats_limited)
self.assertEqual(event.event_ticket_ids.seats_max, 5)
self.assertEqual(event.event_ticket_ids.product_id, self.event_product)
self.assertEqual(event.event_ticket_ids.price, self.event_product.list_price)
@@ -120,10 +120,10 @@ class TestEventTicketData(TestEventSaleCommon):
self.assertEqual(second_ticket.price, 8.0)
# default availability
self.assertEqual(first_ticket.seats_availability, 'limited')
self.assertTrue(first_ticket.seats_limited)
self.assertTrue(first_ticket.sale_available)
self.assertFalse(first_ticket.is_expired)
self.assertEqual(second_ticket.seats_availability, 'unlimited')
self.assertFalse(second_ticket.seats_limited)
self.assertTrue(second_ticket.sale_available)
self.assertFalse(second_ticket.is_expired)
+1 -1
View File
@@ -27,7 +27,7 @@
domain="[
('event_id', '=', event_id),
('product_id','=',product_id),
'|', ('seats_availability', '=', 'unlimited'), ('seats_available', '>', 0), '|', ('company_id', '=', False), ('company_id', '=', parent.company_id)
'|', ('seats_limited', '=', False), ('seats_available', '>', 0), '|', ('company_id', '=', False), ('company_id', '=', parent.company_id)
]"
attrs="{
'invisible': ['|', ('event_ok', '=', False), ('event_id', '=', False)],
@@ -20,7 +20,7 @@
domain="[
('event_id', '=', event_id),
('product_id','=',product_id),
'|', ('seats_availability', '=', 'unlimited'), ('seats_available', '>', 0)
'|', ('seats_limited', '=', False), ('seats_available', '>', 0)
]"
attrs="{
'invisible': [('event_id', '=', False)],
@@ -3,7 +3,7 @@
<record id="event_event_view_form_inherit_mass_mailing_track" model="ir.ui.view">
<field name="name">event.event.view.form.inherit.mass.mailing.track</field>
<field name="model">event.event</field>
<field name="priority" eval="25"/>
<field name="priority" eval="4"/>
<field name="inherit_id" ref="event.view_event_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='stage_id']" position="before">
+2 -6
View File
@@ -100,11 +100,9 @@ class WebsiteEventController(http.Controller):
current_type = EventType.browse(int(searches['type']))
domain_search["type"] = [("event_type_id", "=", int(searches["type"]))]
if searches["country"] != 'all' and searches["country"] != 'online':
if searches["country"] != 'all':
current_country = request.env['res.country'].browse(int(searches['country']))
domain_search["country"] = ['|', ("country_id", "=", int(searches["country"])), ("country_id", "=", False)]
elif searches["country"] == 'online':
domain_search["country"] = [("country_id", "=", False)]
def dom_without(without):
domain = []
@@ -140,8 +138,6 @@ class WebsiteEventController(http.Controller):
order = 'date_begin'
if searches.get('date', 'all') == 'old':
order = 'date_begin desc'
if searches["country"] != 'all': # if we are looking for a specific country
order = 'is_online, ' + order # show physical events first
order = 'is_published desc, ' + order
events = Event.search(dom_without("none"), limit=step, offset=pager['offset'], order=order)
@@ -300,7 +296,7 @@ class WebsiteEventController(http.Controller):
def registration_new(self, event, **post):
tickets = self._process_tickets_form(event, post)
availability_check = True
if event.seats_availability == 'limited':
if event.seats_limited:
ordered_seats = 0
for ticket in tickets:
ordered_seats += ticket['quantity']
+15 -11
View File
@@ -70,12 +70,16 @@ class Event(models.Model):
@api.model
def create(self, vals):
res = super(Event, self).create(vals)
res._update_website_menus(vals)
res._update_website_menus()
return res
def write(self, vals):
menu_activated = self.filtered(lambda event: event.website_menu)
menu_deactivated = self.filtered(lambda event: not event.website_menu)
res = super(Event, self).write(vals)
self._update_website_menus(vals)
menu_to_deactivate = menu_activated.filtered(lambda event: not event.website_menu)
menu_to_activate = menu_deactivated.filtered(lambda event: event.website_menu)
(menu_to_activate | menu_to_deactivate)._update_website_menus()
return res
def _get_menu_entries(self):
@@ -88,19 +92,19 @@ class Event(models.Model):
(_('Register'), '/event/%s/register' % slug(self), False),
]
def _update_website_menus(self, vals):
def _update_website_menus(self):
for event in self:
if 'website_menu' in vals:
if event.menu_id and not event.website_menu:
event.menu_id.unlink()
elif event.website_menu and not event.menu_id:
root_menu = self.env['website.menu'].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.menu_id and not event.website_menu:
event.menu_id.unlink()
elif event.website_menu and not event.menu_id:
root_menu = self.env['website.menu'].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)
def _create_menu(self, sequence, name, url, xml_id):
if not url:
self.env['ir.ui.view'].search([('name', '=', name + ' ' + self.name)]).unlink()
newpath = self.env['website'].new_page(name + ' ' + self.name, template=xml_id, ispage=False)['url']
url = "/event/" + slug(self) + "/page/" + newpath[1:]
menu = self.env['website.menu'].create({
@@ -0,0 +1,95 @@
odoo.define('website_event.set_customize_options', function (require) {
"use strict";
var CustomizeMenu = require('website.customizeMenu');
var publicWidget = require('web.public.widget');
var EventSpecificOptions = publicWidget.Widget.extend({
template: 'website_event.customize_options',
xmlDependencies: ['/website_event/static/src/xml/customize_options.xml'],
events: {
'change #display-website-menu': '_onDisplaySubmenuChange',
},
/**
* @override
*/
start: function () {
this.$submenuInput = this.$('#display-website-menu');
this.modelName = this._getEventObject().model;
this.eventId = this._getEventObject().id;
this._initCheckbox();
},
_initCheckbox: function () {
this._rpc({
model: this.modelName,
method: 'read',
args: [[this.eventId], ['website_menu', 'website_url']],
}).then((data) => {
if (data[0]['website_menu']) {
this.$submenuInput.attr('checked', 'checked');
}
this.eventUrl = data[0]['website_url'];
});
},
_onDisplaySubmenuChange: function (ev) {
var checkboxValue = this.$submenuInput.is(':checked');
this._toggleSubmenuDisplay(checkboxValue);
},
_toggleSubmenuDisplay: function (val) {
var self = this;
this._rpc({
model: this.modelName,
method: 'toggle_website_menu',
args: [[this.eventId], val],
}).then(function () {
self._reloadEventPage();
});
},
_reloadEventPage: function () {
window.location = this.eventUrl;
},
_getEventObject: function() {
var repr = $('html').data('main-object');
var m = repr.match(/(.+)\((\d+),(.*)\)/);
return {
model: m[1],
id: m[2] | 0,
};
}
});
CustomizeMenu.include({
_getEventObject: function() {
var repr = $('html').data('main-object');
var m = repr.match(/(.+)\((\d+),(.*)\)/);
return {
model: m[1],
id: m[2] | 0,
};
},
_loadCustomizeOptions: function () {
var self = this;
var def = this._super.apply(this, arguments);
return def.then(function () {
if (!self.__eventOptionsLoaded && self._getEventObject().model === 'event.event') {
self.__eventOptionsLoaded = true;
self.eventOptions = new EventSpecificOptions(self);
self.eventOptions.insertAfter(self.$el.find('.dropdown-divider:first()'));
}
});
},
});
return {
EventSpecificOptions: EventSpecificOptions,
};
});
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="website_event.customize_options" id="event_options">
<li class="dropdown-header">Event Specific</li>
<a href="#" name="display-website-menu" class="dropdown-item" role="menuitem">
<label class="o_switch" for="display-website-menu">
<input id="display-website-menu" type="checkbox"/>
<span/>
Event Sub-menu
</label>
</a>
</t>
</templates>
+1
View File
@@ -15,6 +15,7 @@
<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>
<script type="text/javascript" src="/website_event/static/src/js/website_event_set_customize_options.js"></script>
</xpath>
</template>
+25 -34
View File
@@ -213,7 +213,6 @@
<!-- Title -->
<h5 t-attf-class="card-title mt-2 mb-0 text-truncate #{(not event.website_published) and 'text-danger'}">
<span t-field="event.name" itemprop="name"/>
<div class="badge badge-pill badge-info ml-2 mt-2 pt4 pb4" t-if="event.is_online">Online</div>
</h5>
<!-- Start Date & Time -->
<time itemprop="startDate" t-att-datetime="event.date_begin">
@@ -225,8 +224,7 @@
<small class="text-muted text-truncate">Organizer: <span t-field="event.organizer_id" itemprop="organizer"/></small>
</div>
<!-- Location -->
<div t-if="not event.is_online" itemprop="location" t-field="event.address_id" t-options="{'widget': 'contact', 'fields': ['city'], 'no_marker': 'true'}"/>
<div t-else="" class="invisible">online</div>
<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'}">
@@ -240,7 +238,7 @@
<footer
t-attf-class="small align-self-end w-100 #{opt_events_list_cards and 'card-footer' or ((not opt_events_list_columns) and 'border-top mx-4 mt-auto pt-2' or 'border-top py-2')}">
<span class="text-danger"
t-if="event.seats_availability == 'limited' and not event.seats_available">
t-if="event.seats_limited and not event.seats_available">
Sold Out
</span>
</footer>
@@ -466,31 +464,24 @@
</div>
</div>
<!-- Location -->
<t t-if="not event.is_online">
<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>
</t>
<t t-else="">
<div class="o_wevent_sidebar_block">
<h6 class="o_wevent_sidebar_title">Online Event</h6>
</div>
</t>
<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>
@@ -596,8 +587,8 @@
<select t-if="not ticket.is_expired and ticket.sale_available"
t-attf-name="nb_register-#{ticket.id}"
class="custom-select">
<t t-set="seats_max_ticket" t-value="(ticket.seats_availability == 'unlimited' or ticket.seats_available &gt; 9) and 10 or ticket.seats_available + 1"/>
<t t-set="seats_max_event" t-value="(event.seats_availability == 'unlimited' or event.seats_available &gt; 9) and 10 or event.seats_available + 1"/>
<t t-set="seats_max_ticket" t-value="(not ticket.seats_limited or ticket.seats_available &gt; 9) and 10 or ticket.seats_available + 1"/>
<t t-set="seats_max_event" t-value="(not event.seats_limited or event.seats_available &gt; 9) and 10 or event.seats_available + 1"/>
<t t-set="seats_max" t-value="min(seats_max_ticket, seats_max_event)"/>
<t t-foreach="range(0, seats_max)" t-as="nb">
<option t-esc="nb" t-att-selected="len(ticket) == 0 and nb == 0 and 'selected'"/>
@@ -612,7 +603,7 @@
</div>
<div class="row no-gutters">
<div class="col-md-4 offset-md-8 py-2 pl-md-0 pr-md-2">
<button type="submit" class="btn btn-primary o_wait_lazy_js btn-block a-submit" disabled="" t-attf-id="#{event.id}">Register<t t-if="event.seats_availability != 'unlimited'">(Available seats: <t t-esc="event.seats_available"/>)</t></button>
<button type="submit" class="btn btn-primary o_wait_lazy_js btn-block a-submit" disabled="" t-attf-id="#{event.id}">Register<t t-if="event.seats_limited">(Available seats: <t t-esc="event.seats_available"/>)</t></button>
</div>
</div>
</div>
@@ -629,8 +620,8 @@
<span class="text-dark font-weight-bold align-middle pr-2">Qty</span>
<link itemprop="availability" content="http://schema.org/InStock"/>
<select t-att-name="'nb_register-%s' % (tickets.id if tickets else 0)" class="w-auto custom-select">
<t t-set="seats_max_ticket" t-value="(not tickets or tickets.seats_availability == 'unlimited' or tickets.seats_available &gt; 9) and 10 or tickets.seats_available + 1"/>
<t t-set="seats_max_event" t-value="(event.seats_availability == 'unlimited' or event.seats_available &gt; 9) and 10 or event.seats_available + 1"/>
<t t-set="seats_max_ticket" t-value="(not tickets or not tickets.seats_limited or tickets.seats_available &gt; 9) and 10 or tickets.seats_available + 1"/>
<t t-set="seats_max_event" t-value="(not event.seats_limited or event.seats_available &gt; 9) and 10 or event.seats_available + 1"/>
<t t-set="seats_max" t-value="min(seats_max_ticket, seats_max_event) if tickets else seats_max_event"/>
<t t-foreach="range(0, seats_max)" t-as="nb">
<option t-esc="nb" t-att-selected="nb == 1 and 'selected'"/>
+1 -6
View File
@@ -31,17 +31,13 @@
<field name="priority" eval="5"/>
<field name="inherit_id" ref="event.view_event_form"/>
<field name="arch" type="xml">
<field name="organizer_id" position="before">
<field name="company_id" position="after">
<field name="website_id" options="{'no_create': True}" domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]" groups="website.group_multi_website"/>
</field>
<div name="button_box" position="inside">
<field name="website_url" invisible="1"/>
<field name="is_published" widget="website_redirect_button"/>
</div>
<label for="is_online" position="after">
<field name="website_menu"/>
<label for="website_menu" string="Website Menu"/>
</label>
<xpath expr="//field[@name='stage_id']" position="before">
<button name="action_open_badge_editor"
type="object"
@@ -57,7 +53,6 @@
<field name="arch" type="xml">
<field name="company_id" position="after">
<field name="website_id" groups="website.group_multi_website" domain="['|', ('company_id', '=', company_id), ('company_id', '=', False)]" optional="show"/>
<field name="is_online" string="Is Online" optional="hide"/>
</field>
</field>
</record>
@@ -60,7 +60,7 @@
<field name="inherit_id" ref="website_event.event_event_view_form_inherit_website"/>
<field name="arch" type="xml">
<data>
<xpath expr="//notebook" position="inside">
<page name="event_notes" position="before">
<page string="Questions" name="questions">
<field name="question_ids" nolabel="1">
<tree>
@@ -71,7 +71,7 @@
</tree>
</field>
</page>
</xpath>
</page>
</data>
</field>
</record>
@@ -67,13 +67,13 @@ class SaleOrder(models.Model):
# case: buying tickets for a sold out ticket
values = {}
if ticket and ticket.seats_availability == 'limited' and ticket.seats_available <= 0:
if ticket and ticket.seats_limited and ticket.seats_available <= 0:
values['warning'] = _('Sorry, The %(ticket)s tickets for the %(event)s event are sold out.') % {
'ticket': ticket.name,
'event': ticket.event_id.name}
new_qty, set_qty, add_qty = 0, 0, -old_qty
# case: buying tickets, too much attendees
elif ticket and ticket.seats_availability == 'limited' and new_qty > ticket.seats_available:
elif ticket and ticket.seats_limited and new_qty > ticket.seats_available:
values['warning'] = _('Sorry, only %(remaining_seats)d seats are still available for the %(ticket)s ticket for the %(event)s event.') % {
'remaining_seats': ticket.seats_available,
'ticket': ticket.name,
@@ -55,7 +55,7 @@ class Event(models.Model):
for event in self:
event.sponsor_count = result.get(event.id, 0)
@api.depends('event_type_id', 'website_menu')
@api.depends('event_type_id', 'website_menu', 'website_track_proposal')
def _compute_website_track(self):
""" Explicitly checks that event_type has changed before copying its value
on the event itself. Changing website_menu trigger should not mess with the
@@ -63,8 +63,10 @@ class Event(models.Model):
for event in self:
if event.event_type_id and event.event_type_id != event._origin.event_type_id:
event.website_track = event.event_type_id.website_track
elif not event.website_menu or not event.website_track:
elif not event.website_menu:
event.website_track = False
elif event.website_track_proposal and not event.website_track:
event.website_track = True
@api.depends('event_type_id', 'website_track')
def _compute_website_track_proposal(self):
@@ -74,57 +76,46 @@ class Event(models.Model):
elif not event.website_track:
event.website_track_proposal = False
@api.onchange('website_track_proposal')
def _onchange_website_track_proposal(self):
""" Keep an explicit onchange for tick / untick of website_track_proposal.
Indeed otherwise you have a loop of dependencies between website_track and
website_track_proposal
* untick website_track: website_track_proposal = False (done in _compute_website_track_proposal)
* tick website_track: no effect
* untick website_track_proposal: no effect
* tick website_track_proposa: website_track = True
It would be complicated to write in computed fields, as they depend on
each other, on cache and current values, ...
It is therefore simpler to keep an onchange: when ticking website_track_proposal
set website_track as True in interface.
"""
for event in self:
if event.website_track_proposal and not event.website_track:
event.website_track = True
@api.depends('track_ids.tag_ids', 'track_ids.tag_ids.color')
def _compute_tracks_tag_ids(self):
for event in self:
event.tracks_tag_ids = event.track_ids.mapped('tag_ids').filtered(lambda tag: tag.color != 0).ids
def _update_website_menus(self, vals):
super(Event, self)._update_website_menus(vals)
def _update_website_menus(self):
super(Event, self)._update_website_menus()
for event in self:
if 'website_track' in vals:
if vals['website_track'] and not event.track_menu_ids:
for sequence, (name, url, xml_id, menu_type) in enumerate(event._get_track_menu_entries()):
menu = super(Event, event)._create_menu(sequence, name, url, xml_id)
event.env['website.event.menu'].create({
'menu_id': menu.id,
'event_id': event.id,
'menu_type': menu_type,
})
elif event.track_menu_ids and not vals['website_track']:
event.track_menu_ids.mapped('menu_id').unlink()
if 'website_track_proposal' in vals:
if vals['website_track_proposal'] and not event.track_proposal_menu_ids:
for sequence, (name, url, xml_id, menu_type) in enumerate(event._get_track_proposal_menu_entries()):
menu = super(Event, event)._create_menu(sequence, name, url, xml_id)
event.env['website.event.menu'].create({
'menu_id': menu.id,
'event_id': event.id,
'menu_type': menu_type,
})
elif event.track_proposal_menu_ids and not vals['website_track_proposal']:
event.track_proposal_menu_ids.mapped('menu_id').unlink()
if event.website_track and not event.track_menu_ids:
for sequence, (name, url, xml_id, menu_type) in enumerate(event._get_track_menu_entries()):
menu = super(Event, event)._create_menu(sequence, name, url, xml_id)
event.env['website.event.menu'].create({
'menu_id': menu.id,
'event_id': event.id,
'menu_type': menu_type,
})
elif not event.website_track:
event.track_menu_ids.mapped('menu_id').unlink()
if event.website_track_proposal and not event.track_proposal_menu_ids:
for sequence, (name, url, xml_id, menu_type) in enumerate(event._get_track_proposal_menu_entries()):
menu = super(Event, event)._create_menu(sequence, name, url, xml_id)
event.env['website.event.menu'].create({
'menu_id': menu.id,
'event_id': event.id,
'menu_type': menu_type,
})
elif not event.website_track_proposal:
event.track_proposal_menu_ids.mapped('menu_id').unlink()
def write(self, values):
track_activated = self.filtered(lambda event: event.website_track)
track_deactivated = self.filtered(lambda event: not event.website_track)
track_proposal_activated = self.filtered(lambda event: event.website_track_proposal)
track_proposal_deactivated = self.filtered(lambda event: not event.website_track_proposal)
super(Event, self).write(values)
to_deactivate = track_activated.filtered(lambda event: not event.website_track)
to_activate = track_deactivated.filtered(lambda event: event.website_track)
track_proposal_to_deactivate = track_proposal_activated.filtered(lambda event: not event.website_track_proposal)
track_proposal_to_activate = track_proposal_deactivated.filtered(lambda event: event.website_track_proposal)
(to_activate | to_deactivate | track_proposal_to_activate | track_proposal_to_deactivate)._update_website_menus()
def _get_track_menu_entries(self):
self.ensure_one()
@@ -137,3 +128,12 @@ class Event(models.Model):
self.ensure_one()
res = [(_('Talk Proposals'), '/event/%s/track_proposal' % slug(self), False, 'track_proposal')]
return res
def toggle_website_menu(self, val):
self.website_menu = val
def toggle_website_track(self, val):
self.website_track = val
def toggle_website_track_proposal(self, val):
self.website_track_proposal = val
@@ -0,0 +1,79 @@
odoo.define('website_event_track.set_customize_options', function (require) {
"use strict";
var EventSpecificOptions = require('website_event.set_customize_options').EventSpecificOptions;
EventSpecificOptions.include({
xmlDependencies: (EventSpecificOptions.prototype.xmlDependencies || [])
.concat([
'/website_event_track/static/src/xml/customize_options.xml',
]),
events: _.extend({}, EventSpecificOptions.prototype.events, {
'change #display-showcase-talks': '_onDisplayTalksChange',
'change #allow-talk-proposal': '_onAllowTalkProposalChange',
}),
start: function () {
this.$displayTalksInput = this.$('#display-showcase-talks');
this.$allowTalksInput = this.$('#allow-talk-proposal');
this._super.apply(this, arguments);
},
_initCheckbox: function () {
this._rpc({
model: this.modelName,
method: 'read',
args: [[this.eventId], ['website_menu', 'website_url', 'website_track', 'website_track_proposal']],
}).then((data) => {
if (data[0]['website_track']) {
this.$displayTalksInput.attr('checked', 'checked');
}
if (data[0]['website_track_proposal']) {
this.$allowTalksInput.attr('checked', 'checked');
}
if (data[0]['website_menu']) {
this.$submenuInput.attr('checked', 'checked');
} else {
this.$displayTalksInput.closest('a').addClass('d-none');
this.$allowTalksInput.closest('a').addClass('d-none');
}
this.eventUrl = data[0]['website_url'];
});
},
_onAllowTalkProposalChange: function () {
var checkboxValue = this.$allowTalksInput.is(':checked');
this._toggleTalkProposal(checkboxValue);
},
_toggleTalkProposal: function (val) {
var self = this;
this._rpc({
model: this.modelName,
method: 'toggle_website_track_proposal',
args: [[this.eventId], val],
}).then(function () {
self._reloadEventPage();
});
},
_onDisplayTalksChange: function () {
var checkboxValue = this.$displayTalksInput.is(':checked');
this._toggleDisplayTalks(checkboxValue);
},
_toggleDisplayTalks: function (val) {
var self = this;
this._rpc({
model: this.modelName,
method: 'toggle_website_track',
args: [[this.eventId], val],
}).then(function () {
self._reloadEventPage();
});
}
});
});
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-extend="website_event.customize_options">
<t t-jquery="a[name='display-website-menu']" t-operation="after">
<a href="#" name="display-showcase-talks" class="dropdown-item" role="menuitem">
<label class="o_switch" for="display-showcase-talks">
<input id="display-showcase-talks" type="checkbox"/>
<span/>
Showcase Talks
</label>
</a>
<a href="#" name="allow-talk-proposal" class="dropdown-item" role="menuitem">
<label class="o_switch" for="allow-talk-proposal">
<input id="allow-talk-proposal" type="checkbox"/>
<span/>
Allow Talks Proposals
</label>
</a>
</t>
</t>
</templates>
@@ -5,6 +5,7 @@
<xpath expr="." position="inside">
<link rel="stylesheet" href="/website_event_track/static/src/css/website_event_track.css"/>
<script type="text/javascript" src="/website_event_track/static/src/js/website_event_track.js"></script>
<script type="text/javascript" src="/website_event_track/static/src/js/website_event_track_set_customize_options.js"></script>
</xpath>
</template>
@@ -20,30 +20,25 @@
<!-- Events Organisation/CONFIGURATION/EVENTS -->
<record id="view_event_form" model="ir.ui.view">
<field name="name">Event Tracks</field>
<field name="inherit_id" ref="website_event.event_event_view_form_inherit_website"/>
<field name="inherit_id" ref="event.view_event_form"/>
<field name="model">event.event</field>
<field name="priority" eval="3"/>
<field name="arch" type="xml">
<field name="is_published" position="before">
<div name="button_box" position="inside">
<button name="%(action_event_track_from_event)d"
type="action"
class="oe_stat_button"
icon="fa-inbox"
icon="fa-microphone"
context="{'default_date': date_begin}">
<field name="track_count" string="Tracks" widget="statinfo"/>
</button>
<button name="%(event_sponsor_action_from_event)d"
type="action"
class="oe_stat_button"
icon="fa-users">
icon="fa-black-tie">
<field name="sponsor_count" string="Sponsors" widget="statinfo"/>
</button>
</field>
<label for="website_menu" position="after">
<field name="website_track" attrs="{'invisible': [('website_menu', '=', False)]}"/>
<label for="website_track" string="Tracks on Website" attrs="{'invisible': [('website_menu', '=', False)]}"/>
<field name="website_track_proposal" attrs="{'invisible': [('website_menu', '=', False)]}"/>
<label for="website_track_proposal" string="Track Proposals on Website" attrs="{'invisible': [('website_menu', '=', False)]}"/>
</label>
</div>
</field>
</record>