diff --git a/addons/event/data/event_data.xml b/addons/event/data/event_data.xml
index a9ab41942a6..0c430e37799 100644
--- a/addons/event/data/event_data.xml
+++ b/addons/event/data/event_data.xml
@@ -21,18 +21,15 @@
-
- Physical Event
-
-
-
-
Ticketing
-
+
+ Conference
+
+
diff --git a/addons/event/data/event_demo.xml b/addons/event/data/event_demo.xml
index 6cf245d577b..086b40eacc2 100644
--- a/addons/event/data/event_demo.xml
+++ b/addons/event/data/event_demo.xml
@@ -5,17 +5,24 @@
Exhibition
-
+
Training
+
-
Sport
+
+
+ US/Pacific
+
+
+
+ Europe/Brussels
@@ -29,7 +36,6 @@
US/Pacific
-
@@ -60,9 +66,8 @@
-
+
- unlimited
@@ -97,7 +102,6 @@
- unlimited
@@ -112,7 +116,6 @@
US/Pacific
-
diff --git a/addons/event/models/event_event.py b/addons/event/models/event_event.py
index e5f89ecf884..301cfb99266 100644
--- a/addons/event/models/event_event.py
+++ b/addons/event/models/event_event.py
@@ -24,21 +24,6 @@ class EventType(models.Model):
_description = 'Event Category'
_order = 'sequence, id'
- @api.model
- def _get_default_event_type_mail_ids(self):
- return [(0, 0, {
- 'notification_type': 'mail',
- 'interval_unit': 'now',
- 'interval_type': 'after_sub',
- 'template_id': self.env.ref('event.event_subscription').id,
- }), (0, 0, {
- 'notification_type': 'mail',
- 'interval_nbr': 10,
- 'interval_unit': 'days',
- 'interval_type': 'before_event',
- 'template_id': self.env.ref('event.event_reminder').id,
- })]
-
name = fields.Char('Event Category', required=True, translate=True)
sequence = fields.Integer()
# tickets
@@ -49,8 +34,9 @@ class EventType(models.Model):
readonly=False, store=True)
# registration
has_seats_limitation = fields.Boolean('Limited Seats')
- default_registration_max = fields.Integer(
- 'Maximum Registrations', default=0,
+ seats_max = fields.Integer(
+ 'Maximum Registrations', compute='_compute_default_registration',
+ copy=True, readonly=False, store=True,
help="It will select this default maximum value when you choose this event")
auto_confirm = fields.Boolean(
'Automatically Confirm Registrations', default=True,
@@ -66,9 +52,28 @@ class EventType(models.Model):
use_mail_schedule = fields.Boolean(
'Automatically Send Emails', default=True)
event_type_mail_ids = fields.One2many(
- 'event.type.mail', 'event_type_id', string='Mail Schedule',
- copy=False,
- default=lambda self: self._get_default_event_type_mail_ids())
+ 'event.type.mail', 'event_type_id',
+ string='Mail Schedule', compute='_compute_event_type_mail_ids',
+ readonly=False, store=True)
+
+ @api.depends('use_mail_schedule')
+ def _compute_event_type_mail_ids(self):
+ for template in self:
+ if not template.use_mail_schedule:
+ template.event_type_mail_ids = [(5, 0)]
+ elif not template.event_type_mail_ids:
+ template.event_type_mail_ids = [(0, 0, {
+ 'notification_type': 'mail',
+ 'interval_unit': 'now',
+ 'interval_type': 'after_sub',
+ 'template_id': self.env.ref('event.event_subscription').id,
+ }), (0, 0, {
+ 'notification_type': 'mail',
+ 'interval_nbr': 10,
+ 'interval_unit': 'days',
+ 'interval_type': 'before_event',
+ 'template_id': self.env.ref('event.event_reminder').id,
+ })]
@api.depends('use_ticket')
def _compute_event_type_ticket_ids(self):
@@ -80,10 +85,11 @@ class EventType(models.Model):
'name': _('Registration'),
})]
- @api.onchange('has_seats_limitation')
- def _onchange_has_seats_limitation(self):
- if not self.has_seats_limitation:
- self.default_registration_max = 0
+ @api.depends('has_seats_limitation')
+ def _compute_default_registration(self):
+ for template in self:
+ if not template.has_seats_limitation:
+ template.seats_max = 0
class EventEvent(models.Model):
@@ -114,10 +120,14 @@ class EventEvent(models.Model):
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
event_type_id = fields.Many2one('event.type', string='Category', ondelete='set null')
color = fields.Integer('Kanban Color Index')
- event_mail_ids = fields.One2many('event.mail', 'event_id', string='Mail Schedule', copy=True)
+ event_mail_ids = fields.One2many(
+ 'event.mail', 'event_id', string='Mail Schedule', copy=True,
+ compute='_compute_from_event_type', readonly=False, store=True)
# Kanban fields
kanban_state = fields.Selection([('normal', 'In Progress'), ('done', 'Done'), ('blocked', 'Blocked')], default='normal')
- kanban_state_label = fields.Char(compute='_compute_kanban_state_label', string='Kanban State Label', tracking=True, store=True)
+ kanban_state_label = fields.Char(
+ string='Kanban State Label', compute='_compute_kanban_state_label',
+ store=True, tracking=True)
stage_id = fields.Many2one(
'event.stage', ondelete='restrict', default=_get_default_stage_id,
group_expand='_read_group_stage_ids', tracking=True)
@@ -127,10 +137,12 @@ class EventEvent(models.Model):
# Seats and computation
seats_max = fields.Integer(
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')],
- 'Maximum Attendees', required=True, default='unlimited')
+ string='Maximum Attendees', required=True,
+ compute='_compute_seats_availability', copy=True, readonly=False, store=True)
seats_reserved = fields.Integer(
string='Reserved Seats',
store=True, readonly=True, compute='_compute_seats')
@@ -147,16 +159,18 @@ class EventEvent(models.Model):
string='Number of Expected Attendees',
compute_sudo=True, readonly=True, compute='_compute_seats')
# Registration fields
- auto_confirm = fields.Boolean(string='Autoconfirm Registrations')
+ auto_confirm = fields.Boolean(
+ string='Autoconfirm Registrations',
+ compute='_compute_from_event_type', copy=True, readonly=False, store=True)
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(
- 'event.event.ticket', 'event_id', string='Event Ticket',
- copy=True)
+ 'event.event.ticket', 'event_id', string='Event Ticket', copy=True,
+ compute='_compute_from_event_type', readonly=False, store=True)
# Date fields
date_tz = fields.Selection(
_tz_get, string='Timezone', required=True,
- default=lambda self: self.env.user.tz or 'UTC')
+ compute='_compute_date_tz', copy=True, readonly=False, store=True)
date_begin = fields.Datetime(string='Start Date', required=True, tracking=True)
date_end = fields.Datetime(string='End Date', required=True, tracking=True)
date_begin_located = fields.Char(string='Start Date Located', compute='_compute_date_begin_tz')
@@ -165,12 +179,16 @@ 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('Online Event')
+ 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', tracking=True,
- default=lambda self: self.env.company.partner_id,
+ 'res.partner', string='Venue', compute='_compute_address_id',
+ copy=True, readonly=False, store=True, tracking=True,
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
- country_id = fields.Many2one('res.country', 'Country', related='address_id.country_id', store=True, readonly=False)
+ country_id = fields.Many2one(
+ 'res.country', 'Country', related='address_id.country_id',
+ copy=True, readonly=False, store=True)
# badge fields
badge_front = fields.Html(string='Badge Front')
badge_back = fields.Html(string='Badge Back')
@@ -282,47 +300,86 @@ 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.onchange('is_online')
- def _onchange_is_online(self):
- if self.is_online:
- self.address_id = 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.onchange('event_type_id')
- def _onchange_type(self):
- if self.event_type_id:
- self.seats_max = self.event_type_id.default_registration_max
- if self.event_type_id.default_registration_max:
- self.seats_availability = 'limited'
+ @api.depends('event_type_id')
+ def _compute_date_tz(self):
+ for event in self:
+ if event.event_type_id.use_timezone:
+ event.date_tz = event.event_type_id.default_timezone
+ if not event.date_tz:
+ event.date_tz = self.env.user.tz or 'UTC'
- if self.event_type_id.auto_confirm:
- self.auto_confirm = self.event_type_id.auto_confirm
+ @api.depends('event_type_id')
+ def _compute_seats_availability(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
+ blacklisted during create (see ``_field_computed`` attribute used in create
+ 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'
- if self.event_type_id.use_timezone:
- self.date_tz = self.event_type_id.default_timezone
+ @api.depends('event_type_id')
+ def _compute_from_event_type(self):
+ """ Update event configuration from its event type. Depends are set only
+ on event_type_id itself, not its sub fields. Indeed purpose is to emulate
+ an onchange: if event type is changed, update event configuration. Changing
+ event type content itself should not trigger this method.
- self.is_online = self.event_type_id.is_online
+ 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)
+ """
+ for event in self:
+ 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
- if self.event_type_id.use_mail_schedule and self.event_type_id.event_type_mail_ids:
- self.event_mail_ids = [(5, 0, 0)] + [
+ if event.event_type_id.seats_max:
+ event.seats_max = event.event_type_id.seats_max
+
+ 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)] + [
(0, 0, {
attribute_name: line[attribute_name] if not isinstance(line[attribute_name], models.BaseModel) else line[attribute_name].id
for attribute_name in self.env['event.type.mail']._get_event_mail_fields_whitelist()
})
- for line in self.event_type_id.event_type_mail_ids]
+ for line in event.event_type_id.event_type_mail_ids]
- # compute tickets information
- if self.event_type_id.use_ticket:
- all_ticket_values = []
- for ticket in self.event_type_id.event_type_ticket_ids:
- ticket_vals = dict(
- (attribute_name, ticket[attribute_name] if not isinstance(ticket[attribute_name], models.BaseModel) else ticket[attribute_name].id)
+ # compute tickets information (force only if activated and tickets defined)
+ if event.event_type_id.use_ticket and event.event_type_id.event_type_ticket_ids:
+ event.event_ticket_ids = [(5, 0, 0)] + [
+ (0, 0, {
+ attribute_name: line[attribute_name] if not isinstance(line[attribute_name], models.BaseModel) else line[attribute_name].id
for attribute_name in self.env['event.type.ticket']._get_event_ticket_fields_whitelist()
- )
- all_ticket_values.append(ticket_vals)
+ })
+ for line in event.event_type_id.event_type_ticket_ids]
- self.event_ticket_ids = [(5, 0, 0)] + [(0, 0, item) for item in all_ticket_values]
-
- @api.constrains('seats_max', 'seats_available')
+ @api.constrains('seats_max', 'seats_available', 'seats_availability')
def _check_seats_limit(self):
if any(event.seats_availability == 'limited' and event.seats_max and event.seats_available < 0 for event in self):
raise ValidationError(_('No more available seats.'))
@@ -350,6 +407,9 @@ 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``
+ vals.update(self._sync_required_computed(vals))
+
res = super(EventEvent, self).create(vals)
if res.organizer_id:
res.message_subscribe([res.organizer_id.id])
@@ -367,6 +427,17 @@ class EventEvent(models.Model):
default = dict(default or {}, name=_("%s (copy)") % (self.name))
return super(EventEvent, self).copy(default)
+ def _sync_required_computed(self, values):
+ """ 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())))
+ if missing_fields and values:
+ cache_event = self.new(values)
+ cache_event._compute_from_event_type()
+ return dict((fname, cache_event[fname]) for fname in missing_fields)
+ else:
+ return {}
+
def action_set_done(self):
"""
Action which will move the events
diff --git a/addons/event/models/event_registration.py b/addons/event/models/event_registration.py
index 5f6c32a620b..e22283172e2 100644
--- a/addons/event/models/event_registration.py
+++ b/addons/event/models/event_registration.py
@@ -29,13 +29,17 @@ class EventRegistration(models.Model):
partner_id = fields.Many2one(
'res.partner', string='Contact',
states={'done': [('readonly', True)]})
- name = fields.Char(string='Attendee Name', index=True, required=True, tracking=True)
- email = fields.Char(string='Email')
- phone = fields.Char(string='Phone')
- mobile = fields.Char(string='Mobile')
+ name = fields.Char(
+ string='Attendee Name', index=True,
+ compute='_compute_contact_info', copy=True, readonly=False, store=True, tracking=10)
+ email = fields.Char(string='Email', compute='_compute_contact_info', copy=True, readonly=False, store=True, tracking=11)
+ phone = fields.Char(string='Phone', compute='_compute_contact_info', copy=True, readonly=False, store=True, tracking=12)
+ mobile = fields.Char(string='Mobile', compute='_compute_contact_info', copy=True, readonly=False, store=True, tracking=13)
# organization
date_open = fields.Datetime(string='Registration Date', readonly=True, default=lambda self: fields.Datetime.now()) # weird crash is directly now
- date_closed = fields.Datetime(string='Attended Date', readonly=True)
+ date_closed = fields.Datetime(
+ string='Attended Date', compute='_compute_date_closed',
+ copy=True, readonly=False, store=True)
event_begin_date = fields.Datetime(string="Event Start Date", related='event_id.date_begin', readonly=True)
event_end_date = fields.Datetime(string="Event End Date", related='event_id.date_end', readonly=True)
company_id = fields.Many2one(
@@ -46,11 +50,43 @@ class EventRegistration(models.Model):
('open', 'Confirmed'), ('done', 'Attended')],
string='Status', default='draft', readonly=True, copy=False, tracking=True)
- @api.onchange('event_id')
- def _onchange_event_id(self):
- # We reset the ticket when keeping it would lead to an inconstitent state.
- if self.event_ticket_id and (not self.event_id or self.event_id != self.event_ticket_id.event_id):
- self.event_ticket_id = None
+ @api.onchange('partner_id')
+ def _onchange_partner_id(self):
+ """ Keep an explicit onchange on partner_id. Rationale : if user explicitly
+ changes the partner in interface, he want to update the whole customer
+ information. If partner_id is updated in code (e.g. updating your personal
+ information after having registered in website_event_sale) fields with a
+ value should not be reset as we don't know which one is the right one.
+
+ In other words
+ * computed fields based on partner_id should only update missing
+ information. Indeed automated code cannot decide which information
+ is more accurate;
+ * interface should allow to update all customer related information
+ at once. We consider event users really want to update all fields
+ related to the partner;
+ """
+ for registration in self:
+ if registration.partner_id:
+ registration.update(registration._synchronize_partner_values(registration.partner_id))
+
+ @api.depends('partner_id')
+ def _compute_contact_info(self):
+ for registration in self:
+ if registration.partner_id:
+ partner_vals = self._synchronize_partner_values(registration.partner_id)
+ registration.update(
+ dict((fname, fvalue)
+ for fname, fvalue in partner_vals.items()
+ if fvalue and not (registration[fname] or registration._origin[fname])
+ )
+ )
+
+ @api.depends('state')
+ def _compute_date_closed(self):
+ for registration in self:
+ if registration.state == 'done' and not registration.date_closed:
+ registration.date_closed = fields.Datetime.now()
@api.constrains('event_id', 'state')
def _check_seats_limit(self):
@@ -64,10 +100,10 @@ class EventRegistration(models.Model):
if record.event_ticket_id.seats_max and record.event_ticket_id.seats_available < 0:
raise ValidationError(_('No more available seats for this ticket'))
- @api.onchange('partner_id')
- def _onchange_partner(self):
- if self.partner_id:
- self.update(self._synchronize_partner_values(self.partner_id))
+ @api.constrains('event_id', 'event_ticket_id')
+ def _check_event_ticket(self):
+ if any(registration.event_id != registration.event_ticket_id.event_id for registration in self if registration.event_ticket_id):
+ raise ValidationError(_('Invalid event / ticket choice'))
# ------------------------------------------------------------
# CRUD
@@ -75,13 +111,6 @@ class EventRegistration(models.Model):
@api.model
def create(self, vals):
- # update missing pieces of information from partner
- if vals.get('partner_id'):
- partner_vals = self._synchronize_partner_values(
- self.env['res.partner'].browse(vals['partner_id'])
- )
- vals = dict(partner_vals, **vals)
-
registration = super(EventRegistration, self).create(vals)
if registration._check_auto_confirmation():
registration.sudo().action_confirm()
@@ -89,21 +118,8 @@ class EventRegistration(models.Model):
return registration
def write(self, vals):
- if vals.get('state') == 'done' and 'date_closed' not in vals:
- vals['date_closed'] = fields.Datetime.now()
-
ret = super(EventRegistration, self).write(vals)
- # update missing pieces of information from partner
- if vals.get('partner_id'):
- partner_vals = self._synchronize_partner_values(
- self.env['res.partner'].browse(vals['partner_id'])
- )
- for registration in self:
- partner_info = dict((key, val) for key, val in partner_vals.items() if not registration[key])
- if partner_info:
- registration.write(partner_info)
-
if vals.get('state') == 'open':
# auto-trigger after_sub (on subscribe) mail schedulers, if needed
onsubscribe_schedulers = self.mapped('event_id.event_mail_ids').filtered(lambda s: s.interval_type == 'after_sub')
diff --git a/addons/event/tests/common.py b/addons/event/tests/common.py
index f58722efcd1..62509636ed9 100644
--- a/addons/event/tests/common.py
+++ b/addons/event/tests/common.py
@@ -53,12 +53,12 @@ class TestEventCommon(common.SavepointCase):
'auto_confirm': True,
'is_online': False,
'has_seats_limitation': True,
- 'default_registration_max': 30,
+ 'seats_max': 30,
'use_timezone': True,
'default_timezone': 'Europe/Paris',
'use_ticket': True,
'event_type_ticket_ids': [(0, 0, {
- 'name': 'First Ticket',
+ 'name': 'First Ticket',
}), (0, 0, {
'name': 'Second Ticket',
})
diff --git a/addons/event/tests/test_event_flow.py b/addons/event/tests/test_event_flow.py
index 572e64a8231..ff114bb4196 100644
--- a/addons/event/tests/test_event_flow.py
+++ b/addons/event/tests/test_event_flow.py
@@ -7,9 +7,69 @@ from dateutil.relativedelta import relativedelta
from odoo.addons.event.tests.common import TestEventCommon
from odoo.exceptions import ValidationError
+from odoo.tests.common import Form
from odoo.tools import mute_logger
+class TestEventUI(TestEventCommon):
+
+ def test_event_registration_partner_sync(self):
+ """ Ensure onchange on partner_id is kept for interface, not for computed
+ fields. """
+ registration_form = Form(self.env['event.registration'].with_context(
+ default_name='WrongName',
+ default_event_id=self.event_0.id
+ ))
+ self.assertEqual(registration_form.event_id, self.event_0)
+ self.assertEqual(registration_form.name, 'WrongName')
+ self.assertFalse(registration_form.email)
+ self.assertFalse(registration_form.phone)
+ self.assertFalse(registration_form.mobile)
+
+ # trigger onchange
+ registration_form.partner_id = self.customer
+ self.assertEqual(registration_form.name, self.customer.name)
+ self.assertEqual(registration_form.email, self.customer.email)
+ self.assertEqual(registration_form.phone, self.customer.phone)
+ self.assertEqual(registration_form.mobile, self.customer.mobile)
+
+ # save, check record matches Form values
+ registration = registration_form.save()
+ self.assertEqual(registration.partner_id, self.customer)
+ self.assertEqual(registration.name, self.customer.name)
+ self.assertEqual(registration.email, self.customer.email)
+ self.assertEqual(registration.phone, self.customer.phone)
+ self.assertEqual(registration.mobile, self.customer.mobile)
+
+ # allow writing on some fields independently from customer config
+ registration.write({'phone': False, 'mobile': False})
+ self.assertFalse(registration.phone)
+ self.assertFalse(registration.mobile)
+
+ # reset partner should not reset other fields
+ registration.write({'partner_id': False})
+ self.assertEqual(registration.partner_id, self.env['res.partner'])
+ self.assertEqual(registration.name, self.customer.name)
+ self.assertEqual(registration.email, self.customer.email)
+ self.assertFalse(registration.phone)
+ self.assertFalse(registration.mobile)
+
+ # update to a new partner not through UI -> update only void feilds
+ customer2 = self.env['res.partner'].create({
+ 'name': 'Constantin Customer 2',
+ 'email': 'constantin2@test.example.com',
+ 'country_id': self.env.ref('base.be').id,
+ 'phone': '0456987654',
+ 'mobile': '0456654321',
+ })
+ registration.write({'partner_id': customer2.id})
+ self.assertEqual(registration.partner_id, customer2)
+ self.assertEqual(registration.name, self.customer.name)
+ self.assertEqual(registration.email, self.customer.email)
+ self.assertEqual(registration.phone, customer2.phone)
+ self.assertEqual(registration.mobile, customer2.mobile)
+
+
class TestEventFlow(TestEventCommon):
@mute_logger('odoo.addons.base.models.ir_model', 'odoo.models')
@@ -24,6 +84,7 @@ class TestEventFlow(TestEventCommon):
'seats_max': 2,
'seats_availability': 'limited',
})
+ self.assertTrue(test_event.auto_confirm)
# EventUser create registrations for this event
test_reg1 = self.env['event.registration'].with_user(self.user_eventuser).create({
@@ -56,15 +117,17 @@ class TestEventFlow(TestEventCommon):
@mute_logger('odoo.addons.base.models.ir_model', 'odoo.models')
def test_event_flow(self):
- """ Avanced event flow: no auto confirmation, manage minimum / maximum
+ """ Advanced event flow: no auto confirmation, manage minimum / maximum
seats, ... """
# EventUser creates a new event: ok
test_event = self.env['event.event'].with_user(self.user_eventmanager).create({
'name': 'TestEvent',
'date_begin': datetime.datetime.now() + relativedelta(days=-1),
'date_end': datetime.datetime.now() + relativedelta(days=1),
+ 'seats_availability': 'limited',
'seats_max': 10,
})
+ self.assertFalse(test_event.auto_confirm)
# EventUser create registrations for this event -> no auto confirmation
test_reg1 = self.env['event.registration'].with_user(self.user_eventuser).create({
diff --git a/addons/event/tests/test_event_internals.py b/addons/event/tests/test_event_internals.py
index 95e935972b3..1a381f3f37b 100644
--- a/addons/event/tests/test_event_internals.py
+++ b/addons/event/tests/test_event_internals.py
@@ -58,12 +58,12 @@ class TestEventData(TestEventCommon):
'date_begin': FieldsDatetime.to_string(datetime.today() + timedelta(days=1)),
'date_end': FieldsDatetime.to_string(datetime.today() + timedelta(days=15)),
})
- event._onchange_type()
+
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.assertEqual(event.seats_available, event.event_type_id.default_registration_max)
+ 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)
@@ -71,7 +71,6 @@ class TestEventData(TestEventCommon):
# set is_online: should reset the address_id field
event.update({'is_online': True})
- event._onchange_is_online()
self.assertTrue(event.is_online)
self.assertFalse(event.address_id)
@@ -93,7 +92,7 @@ class TestEventData(TestEventCommon):
'name': 'reg_done',
})
reg_done.write({'state': 'done'})
- self.assertEqual(event.seats_available, event.event_type_id.default_registration_max - 6)
+ self.assertEqual(event.seats_available, event.event_type_id.seats_max - 6)
self.assertEqual(event.seats_unconfirmed, 1)
self.assertEqual(event.seats_reserved, 5)
self.assertEqual(event.seats_used, 1)
@@ -112,14 +111,12 @@ class TestEventData(TestEventCommon):
event_type.write({
'use_mail_schedule': False,
})
- # Event type does not use mail schedule but data is kept for compatibility and avoid recreating them
- self.assertEqual(len(event_type.event_type_mail_ids), 2)
+ self.assertEqual(event_type.event_type_mail_ids, self.env['event.type.mail'])
event = self.env['event.event'].create({
'name': 'Event Update Type',
'date_begin': FieldsDatetime.to_string(datetime.today() + timedelta(days=1)),
'date_end': FieldsDatetime.to_string(datetime.today() + timedelta(days=15)),
- 'auto_confirm': False,
'is_online': True,
})
self.assertEqual(event.date_tz, self.env.user.tz)
@@ -128,22 +125,18 @@ class TestEventData(TestEventCommon):
self.assertTrue(event.is_online)
self.assertEqual(event.event_mail_ids, self.env['event.mail'])
- event.update({'event_type_id': event_type.id})
- event._onchange_type()
- self.assertEqual(event.date_tz, 'Europe/Paris')
- self.assertEqual(event.seats_availability, 'limited')
- self.assertEqual(event.seats_max, event_type.default_registration_max)
- self.assertTrue(event.auto_confirm)
- self.assertFalse(event.is_online)
- self.assertEqual(event.event_mail_ids, self.env['event.mail'])
-
event_type.write({
'use_mail_schedule': True,
'event_type_mail_ids': [(5, 0), (0, 0, {
'interval_nbr': 1, 'interval_unit': 'days', 'interval_type': 'before_event',
'template_id': self.env['ir.model.data'].xmlid_to_res_id('event.event_reminder')})]
})
- event._onchange_type()
+ event.write({'event_type_id': event_type.id})
+ self.assertEqual(event.date_tz, 'Europe/Paris')
+ self.assertEqual(event.seats_availability, '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')
@@ -194,7 +187,7 @@ class TestEventData(TestEventCommon):
self.assertFalse(event.event_registrations_open)
@users('user_eventmanager')
- def test_ongoing_events(self):
+ def test_event_ongoing(self):
self.patcher = patch('odoo.addons.event.models.event_event.fields.Datetime', wraps=FieldsDatetime)
self.mock_datetime = self.patcher.start()
self.mock_datetime.now.return_value = datetime(2020, 1, 10, 8, 0, 0)
@@ -294,19 +287,17 @@ class TestEventTypeData(TestEventCommon):
event_type = self.env['event.type'].create({
'name': 'Testing fields computation',
'has_seats_limitation': True,
- 'default_registration_max': 30,
+ 'seats_max': 30,
'use_ticket': True,
})
- event_type._onchange_has_seats_limitation()
self.assertTrue(event_type.has_seats_limitation)
- self.assertEqual(event_type.default_registration_max, 30)
+ self.assertEqual(event_type.seats_max, 30)
self.assertEqual(event_type.event_type_ticket_ids.mapped('name'), ['Registration'])
# reset seats limitation
event_type.write({'has_seats_limitation': False})
- event_type._onchange_has_seats_limitation()
self.assertFalse(event_type.has_seats_limitation)
- self.assertEqual(event_type.default_registration_max, 0)
+ self.assertEqual(event_type.seats_max, 0)
# reset tickets
event_type.write({'use_ticket': False})
diff --git a/addons/event/tests/test_event_mail_schedule.py b/addons/event/tests/test_event_mail_schedule.py
index ba1223ccc0f..42e1d18017d 100644
--- a/addons/event/tests/test_event_mail_schedule.py
+++ b/addons/event/tests/test_event_mail_schedule.py
@@ -22,7 +22,6 @@ class TestMailSchedule(TestEventCommon):
'auto_confirm': True,
'date_begin': event_date_begin,
'date_end': event_date_end,
- 'seats_max': 10,
'event_mail_ids': [
(0, 0, { # right at subscription
'interval_unit': 'now',
diff --git a/addons/event/tests/test_event_security.py b/addons/event/tests/test_event_security.py
index d2ac6d275a3..50cf81a84ec 100644
--- a/addons/event/tests/test_event_security.py
+++ b/addons/event/tests/test_event_security.py
@@ -26,6 +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_max': 10,
})
@@ -50,7 +51,6 @@ class TestEventSecurity(TestEventCommon):
'name': 'TestEvent',
'date_begin': datetime.now() + relativedelta(days=-1),
'date_end': datetime.now() + relativedelta(days=1),
- 'seats_max': 10,
})
with self.assertRaises(AccessError):
@@ -76,12 +76,10 @@ class TestEventSecurity(TestEventCommon):
})
event = self.env['event.event'].create({
'name': 'ManagerEvent',
+ 'event_type_id': event_type.id,
'date_begin': datetime.now() + relativedelta(days=-1),
'date_end': datetime.now() + relativedelta(days=1),
})
- event.update({'event_type_id': event_type.id})
- event._onchange_type()
- event.flush()
registration = self.env['event.registration'].create({'event_id': event.id, 'name': 'Myself'})
registration.write({'name': 'Myself2'})
diff --git a/addons/event/views/event_views.xml b/addons/event/views/event_views.xml
index ed6fbbb926c..8a18a767fc5 100644
--- a/addons/event/views/event_views.xml
+++ b/addons/event/views/event_views.xml
@@ -80,7 +80,7 @@
diff --git a/addons/event_sale/models/event_registration.py b/addons/event_sale/models/event_registration.py
index 3c8b4c780a9..84144973885 100644
--- a/addons/event_sale/models/event_registration.py
+++ b/addons/event_sale/models/event_registration.py
@@ -73,13 +73,12 @@ class EventRegistration(models.Model):
'old_ticket_name': registration.event_ticket_id.name,
'new_ticket_name': new_event_ticket.name
}
- user_id = registration.event_id.user_id.id or \
- registration.sale_order_id.user_id.id or \
- fallback_user_id
- registration.sale_order_id.activity_schedule_with_view('mail.mail_activity_data_warning',
- user_id=user_id,
- views_or_xmlid='event_sale.event_ticket_id_change_exception',
- render_context=render_context)
+ user_id = registration.event_id.user_id.id or registration.sale_order_id.user_id.id or fallback_user_id
+ registration.sale_order_id.activity_schedule_with_view(
+ 'mail.mail_activity_data_warning',
+ user_id=user_id,
+ views_or_xmlid='event_sale.event_ticket_id_change_exception',
+ render_context=render_context)
def _action_set_paid(self):
self.write({'is_paid': True})
diff --git a/addons/event_sale/models/event_ticket.py b/addons/event_sale/models/event_ticket.py
index e3e666d5db3..d88ea5f4a24 100644
--- a/addons/event_sale/models/event_ticket.py
+++ b/addons/event_sale/models/event_ticket.py
@@ -14,25 +14,41 @@ class EventTemplateTicket(models.Model):
def _default_product_id(self):
return self.env.ref('event_sale.product_product_event', raise_if_not_found=False)
+ description = fields.Text(compute='_compute_description', copy=True, readonly=False, store=True)
# product
product_id = fields.Many2one(
- 'product.product', string='Product', required=True, domain=[("event_ok", "=", True)],
- default=_default_product_id)
- price = fields.Float(string='Price', digits='Product Price')
- price_reduce = fields.Float(string="Price Reduce", compute="_compute_price_reduce", digits='Product Price')
+ 'product.product', string='Product', required=True,
+ domain=[("event_ok", "=", True)], default=_default_product_id)
+ price = fields.Float(
+ string='Price', compute='_compute_price',
+ digits='Product Price', copy=True, readonly=False, store=True)
+ price_reduce = fields.Float(
+ string="Price Reduce", compute="_compute_price_reduce",
+ digits='Product Price')
@api.depends('product_id')
- def _compute_price_reduce(self):
- for record in self:
- product = record.product_id
- discount = product.lst_price and (product.lst_price - product.price) / product.lst_price or 0.0
- record.price_reduce = (1.0 - discount) * record.price
+ def _compute_price(self):
+ for ticket in self:
+ if ticket.product_id and ticket.product_id.lst_price:
+ ticket.price = ticket.product_id.lst_price or 0
+ elif not ticket.price:
+ ticket.price = 0
- @api.onchange('product_id')
- def _onchange_product_id(self):
- self.price = self.product_id.list_price or 0
- if not self.description and self.product_id.description_sale:
- self.description = self.product_id.description_sale
+ @api.depends('product_id')
+ def _compute_description(self):
+ for ticket in self:
+ if ticket.product_id and ticket.product_id.description_sale:
+ ticket.description = ticket.product_id.description_sale
+ # initialize, i.e for embedded tree views
+ if not ticket.description:
+ ticket.description = False
+
+ @api.depends('product_id', 'price')
+ def _compute_price_reduce(self):
+ for ticket in self:
+ product = ticket.product_id
+ discount = product.lst_price and (product.lst_price - product.price) / product.lst_price or 0.0
+ ticket.price_reduce = (1.0 - discount) * ticket.price
def _init_column(self, column_name):
if column_name != "product_id":
diff --git a/addons/event_sale/tests/test_event_internals.py b/addons/event_sale/tests/test_event_internals.py
index d70911b1524..fc6341b51a5 100644
--- a/addons/event_sale/tests/test_event_internals.py
+++ b/addons/event_sale/tests/test_event_internals.py
@@ -28,7 +28,6 @@ class TestEventData(TestEventSaleCommon):
'date_begin': FieldsDatetime.to_string(datetime.today() + timedelta(days=1)),
'date_end': FieldsDatetime.to_string(datetime.today() + timedelta(days=15)),
})
- event._onchange_type()
self.assertEqual(event.event_ticket_ids, self.env['event.event.ticket'])
event_type.write({
@@ -39,11 +38,10 @@ class TestEventData(TestEventSaleCommon):
'seats_max': 5,
})]
})
- event_type.event_type_ticket_ids._onchange_product_id()
self.assertEqual(event_type.event_type_ticket_ids.description, self.event_product.description_sale)
# synchronize event
- event._onchange_type()
+ 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.assertEqual(event.event_ticket_ids.seats_max, 5)
@@ -113,9 +111,7 @@ class TestEventTicketData(TestEventSaleCommon):
],
})
first_ticket = event.event_ticket_ids.filtered(lambda t: t.name == 'First Ticket')
- first_ticket._onchange_product_id()
second_ticket = event.event_ticket_ids.filtered(lambda t: t.name == 'Second Ticket')
- second_ticket._onchange_product_id()
# force second ticket price, after calling the onchange
second_ticket.write({'price': 8.0})
diff --git a/addons/website_event/models/event_event.py b/addons/website_event/models/event_event.py
index 491b8b60b17..6cd2a7d5bd2 100644
--- a/addons/website_event/models/event_event.py
+++ b/addons/website_event/models/event_event.py
@@ -21,6 +21,11 @@ class Event(models.Model):
_name = 'event.event'
_inherit = ['event.event', 'website.seo.metadata', 'website.published.multi.mixin', 'website.cover_properties.mixin']
+ def _default_cover_properties(self):
+ res = super()._default_cover_properties()
+ res['opacity'] = '0.4'
+ return res
+
# description
subtitle = fields.Char('Event Subtitle', translate=True)
# registration
@@ -28,9 +33,10 @@ class Event(models.Model):
# website
website_published = fields.Boolean(tracking=True)
website_menu = fields.Boolean(
- 'Dedicated Menu', copy=False,
+ string='Dedicated Menu',
+ compute='_compute_from_event_type', readonly=False, store=True,
help="Creates menus Introduction, Location and Register on the page "
- " of the event on the website.")
+ "of the event on the website.")
menu_id = fields.Many2one('website.menu', 'Event Menu', copy=False)
def _compute_is_participating(self):
@@ -50,15 +56,26 @@ class Event(models.Model):
if event.id: # avoid to perform a slug on a not yet saved record in case of an onchange.
event.website_url = '/event/%s' % slug(event)
- @api.onchange('event_type_id')
- def _onchange_type(self):
- super(Event, self)._onchange_type()
- if self.event_type_id:
- self.website_menu = self.event_type_id.website_menu
+ @api.depends('event_type_id')
+ def _compute_from_event_type(self):
+ """ Also ensure a value for website_menu as it is a trigger notably for
+ track related menus. """
+ super(Event, self)._compute_from_event_type()
+ for event in self:
+ if not event.event_type_id and not event.website_menu:
+ event.website_menu = False
+ elif event.event_type_id:
+ event.website_menu = event.event_type_id.website_menu
- def _default_cover_properties(self):
- res = super()._default_cover_properties()
- res['opacity'] = '0.4'
+ @api.model
+ def create(self, vals):
+ res = super(Event, self).create(vals)
+ res._update_website_menus(vals)
+ return res
+
+ def write(self, vals):
+ res = super(Event, self).write(vals)
+ self._update_website_menus(vals)
return res
def _get_menu_entries(self):
@@ -71,7 +88,7 @@ class Event(models.Model):
(_('Register'), '/event/%s/register' % slug(self), False),
]
- def _toggle_create_website_menus(self, vals):
+ def _update_website_menus(self, vals):
for event in self:
if 'website_menu' in vals:
if event.menu_id and not event.website_menu:
@@ -83,17 +100,6 @@ class Event(models.Model):
for sequence, (name, url, xml_id) in enumerate(event._get_menu_entries()):
event._create_menu(sequence, name, url, xml_id)
- @api.model
- def create(self, vals):
- res = super(Event, self).create(vals)
- res._toggle_create_website_menus(vals)
- return res
-
- def write(self, vals):
- res = super(Event, self).write(vals)
- self._toggle_create_website_menus(vals)
- return res
-
def _create_menu(self, sequence, name, url, xml_id):
if not url:
newpath = self.env['website'].new_page(name + ' ' + self.name, template=xml_id, ispage=False)['url']
@@ -107,12 +113,6 @@ class Event(models.Model):
})
return menu
- def google_map_img(self, zoom=8, width=298, height=298):
- self.ensure_one()
- if self.address_id:
- return self.sudo().address_id.google_map_img(zoom=zoom, width=width, height=height)
- return None
-
def google_map_link(self, zoom=8):
self.ensure_one()
if self.address_id:
diff --git a/addons/website_event_questions/__manifest__.py b/addons/website_event_questions/__manifest__.py
index b4cddc33bc4..099abeef1a7 100644
--- a/addons/website_event_questions/__manifest__.py
+++ b/addons/website_event_questions/__manifest__.py
@@ -13,6 +13,7 @@
'security/ir.model.access.csv',
],
'demo': [
+ 'data/event_question_demo.xml',
'data/event_demo.xml',
'data/event_registration_demo.xml',
],
diff --git a/addons/website_event_questions/data/event_demo.xml b/addons/website_event_questions/data/event_demo.xml
index 0a8b5e4f31b..cd199671613 100644
--- a/addons/website_event_questions/data/event_demo.xml
+++ b/addons/website_event_questions/data/event_demo.xml
@@ -1,23 +1,8 @@
-
- Meal Type
-
-
-
-
- Mixed
- 1
-
-
-
- Vegetarian
- 2
-
-
-
- Pastafarian
- 3
-
+
+
+ Europe/Brussels
+
\ No newline at end of file
diff --git a/addons/website_event_questions/data/event_question_demo.xml b/addons/website_event_questions/data/event_question_demo.xml
new file mode 100644
index 00000000000..414ddb44735
--- /dev/null
+++ b/addons/website_event_questions/data/event_question_demo.xml
@@ -0,0 +1,41 @@
+
+
+
+
+ Participate to Social Event
+
+
+
+
+ Yes
+ 1
+
+
+
+ No
+ 2
+
+
+
+
+
+ Meal Type
+
+
+
+
+ Mixed
+ 1
+
+
+
+ Vegetarian
+ 2
+
+
+
+ Pastafarian
+ 3
+
+
+
diff --git a/addons/website_event_questions/models/event_event.py b/addons/website_event_questions/models/event_event.py
index 53e1cfc0533..3a7984a30a0 100644
--- a/addons/website_event_questions/models/event_event.py
+++ b/addons/website_event_questions/models/event_event.py
@@ -17,21 +17,24 @@ class EventEvent(models.Model):
""" Override Event model to add optional questions when buying tickets. """
_inherit = 'event.event'
- question_ids = fields.One2many('event.question', 'event_id', 'Questions', copy=True)
+ question_ids = fields.One2many(
+ 'event.question', 'event_id', 'Questions', copy=True,
+ compute='_compute_from_event_type', readonly=False, store=True)
general_question_ids = fields.One2many('event.question', 'event_id', 'General Questions',
domain=[('once_per_order', '=', True)])
specific_question_ids = fields.One2many('event.question', 'event_id', 'Specific Questions',
domain=[('once_per_order', '=', False)])
- @api.onchange('event_type_id')
- def _onchange_type(self):
- super(EventEvent, self)._onchange_type()
- if self.event_type_id.use_questions and self.event_type_id.question_ids:
- self.question_ids = [(5, 0, 0)] + [
- (0, 0, {
- 'title': question.title,
- 'sequence': question.sequence,
- 'once_per_order': question.once_per_order,
- })
- for question in self.event_type_id.question_ids
- ]
+ @api.depends('event_type_id')
+ def _compute_from_event_type(self):
+ super(EventEvent, self)._compute_from_event_type()
+ for event in self:
+ if event.event_type_id.use_questions and event.event_type_id.question_ids:
+ event.question_ids = [(5, 0, 0)] + [
+ (0, 0, {
+ 'title': question.title,
+ 'sequence': question.sequence,
+ 'once_per_order': question.once_per_order,
+ })
+ for question in event.event_type_id.question_ids
+ ]
diff --git a/addons/website_event_questions/tests/test_event_internals.py b/addons/website_event_questions/tests/test_event_internals.py
index aedaf148fc9..d3494cae49c 100644
--- a/addons/website_event_questions/tests/test_event_internals.py
+++ b/addons/website_event_questions/tests/test_event_internals.py
@@ -22,7 +22,7 @@ class TestEventData(TestEventQuestionCommon):
'date_begin': FieldsDatetime.to_string(datetime.today() + timedelta(days=1)),
'date_end': FieldsDatetime.to_string(datetime.today() + timedelta(days=15)),
})
- event._onchange_type()
+
self.assertEqual(event.specific_question_ids.title, 'Question1')
self.assertEqual(
set(event.specific_question_ids.mapped('answer_ids.name')),
diff --git a/addons/website_event_sale/tests/__init__.py b/addons/website_event_sale/tests/__init__.py
index 2235dde4188..9f8e0cdceb8 100644
--- a/addons/website_event_sale/tests/__init__.py
+++ b/addons/website_event_sale/tests/__init__.py
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from . import test_ui
+from . import test_frontend_buy_tickets
diff --git a/addons/website_event_sale/tests/test_ui.py b/addons/website_event_sale/tests/test_frontend_buy_tickets.py
similarity index 89%
rename from addons/website_event_sale/tests/test_ui.py
rename to addons/website_event_sale/tests/test_frontend_buy_tickets.py
index 60092d1bf09..a64dca5d4ad 100644
--- a/addons/website_event_sale/tests/test_ui.py
+++ b/addons/website_event_sale/tests/test_frontend_buy_tickets.py
@@ -4,9 +4,11 @@
import odoo.tests
from datetime import timedelta
+
from odoo.addons.base.tests.common import HttpCaseWithUserDemo
from odoo.fields import Datetime
+
@odoo.tests.common.tagged('post_install', '-at_install')
class TestUi(HttpCaseWithUserDemo):
@@ -15,8 +17,8 @@ class TestUi(HttpCaseWithUserDemo):
self.event_2 = self.env['event.event'].create({
'name': 'Conference for Architects TEST',
'user_id': self.env.ref('base.user_admin').id,
- 'date_begin': (Datetime.today()+ timedelta(days=5)).strftime('%Y-%m-%d 07:00:00'),
- 'date_end': (Datetime.today()+ timedelta(days=5)).strftime('%Y-%m-%d 16:30:00'),
+ 'date_begin': (Datetime.today() + timedelta(days=5)).strftime('%Y-%m-%d 07:00:00'),
+ 'date_end': (Datetime.today() + timedelta(days=5)).strftime('%Y-%m-%d 16:30:00'),
})
self.env['event.event.ticket'].create([{
@@ -34,6 +36,9 @@ class TestUi(HttpCaseWithUserDemo):
'price': 1500.0,
}])
+ # flush event to ensure having tickets available in the tests
+ self.event_2.flush()
+
(self.env.ref('base.partner_admin') + self.partner_demo).write({
'street': '215 Vine St',
'city': 'Scranton',
diff --git a/addons/website_event_track/data/event_data.xml b/addons/website_event_track/data/event_data.xml
index 772b0673ed2..2f32534c80a 100644
--- a/addons/website_event_track/data/event_data.xml
+++ b/addons/website_event_track/data/event_data.xml
@@ -1,10 +1,7 @@
-
- Conference
-
-
+
diff --git a/addons/website_event_track/models/__init__.py b/addons/website_event_track/models/__init__.py
index 1a9f0aa1fe0..38501492c72 100644
--- a/addons/website_event_track/models/__init__.py
+++ b/addons/website_event_track/models/__init__.py
@@ -5,3 +5,4 @@ from . import event_event
from . import event_sponsor
from . import event_track
from . import event_track_stage
+from . import website_event_menu
diff --git a/addons/website_event_track/models/event_event.py b/addons/website_event_track/models/event_event.py
index 1c75e41f857..20ef5f43c97 100644
--- a/addons/website_event_track/models/event_event.py
+++ b/addons/website_event_track/models/event_event.py
@@ -8,23 +8,19 @@ from odoo.addons.http_routing.models.ir_http import slug
class EventType(models.Model):
_inherit = 'event.type'
- website_track = fields.Boolean('Tracks on Website')
- website_track_proposal = fields.Boolean('Tracks Proposals on Website')
+ website_track = fields.Boolean(
+ string='Tracks on Website', compute='_compute_website_menu_data',
+ copy=True, readonly=False, store=True)
+ website_track_proposal = fields.Boolean(
+ string='Tracks Proposals on Website', compute='_compute_website_menu_data',
+ copy=True, readonly=False, store=True)
- @api.onchange('website_menu')
- def _onchange_website_menu(self):
- if not self.website_menu:
- self.website_track = False
- self.website_track_proposal = False
-
-
-class EventMenu(models.Model):
- _name = "website.event.menu"
- _description = "Website Event Menu"
-
- menu_id = fields.Many2one('website.menu', string='Menu', ondelete='cascade')
- event_id = fields.Many2one('event.event', string='Event', ondelete='cascade')
- menu_type = fields.Selection([('track', 'Event Tracks Menus'), ('track_proposal', 'Event Proposals Menus')])
+ @api.depends('website_menu')
+ def _compute_website_menu_data(self):
+ for event_type in self:
+ if not event_type.website_menu:
+ event_type.website_track = False
+ event_type.website_track_proposal = False
class Event(models.Model):
@@ -32,16 +28,16 @@ class Event(models.Model):
track_ids = fields.One2many('event.track', 'event_id', 'Tracks')
track_count = fields.Integer('Track Count', compute='_compute_track_count')
-
sponsor_ids = fields.One2many('event.sponsor', 'event_id', 'Sponsors')
sponsor_count = fields.Integer('Sponsor Count', compute='_compute_sponsor_count')
-
- website_track = fields.Boolean('Tracks on Website')
- website_track_proposal = fields.Boolean('Proposals on Website')
-
+ website_track = fields.Boolean(
+ 'Tracks on Website', compute='_compute_website_track',
+ copy=True, readonly=False, store=True)
+ website_track_proposal = fields.Boolean(
+ 'Proposals on Website', compute='_compute_website_track_proposal',
+ copy=True, readonly=False, store=True)
track_menu_ids = fields.One2many('website.event.menu', 'event_id', string='Event Tracks Menus', domain=[('menu_type', '=', 'track')])
track_proposal_menu_ids = fields.One2many('website.event.menu', 'event_id', string='Event Proposals Menus', domain=[('menu_type', '=', 'track_proposal')])
-
allowed_track_tag_ids = fields.Many2many('event.track.tag', relation='event_allowed_track_tags_rel', string='Available Track Tags')
tracks_tag_ids = fields.Many2many(
'event.track.tag', relation='event_track_tags_rel', string='Track Tags',
@@ -59,8 +55,53 @@ class Event(models.Model):
for event in self:
event.sponsor_count = result.get(event.id, 0)
- def _toggle_create_website_menus(self, vals):
- super(Event, self)._toggle_create_website_menus(vals)
+ @api.depends('event_type_id', 'website_menu')
+ 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
+ behavior of event_type. """
+ 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:
+ event.website_track = False
+
+ @api.depends('event_type_id', 'website_track')
+ def _compute_website_track_proposal(self):
+ for event in self:
+ if event.event_type_id and event.event_type_id != event._origin.event_type_id:
+ event.website_track_proposal = event.event_type_id.website_track_proposal
+ 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)
for event in self:
if 'website_track' in vals:
if vals['website_track']:
@@ -96,31 +137,3 @@ class Event(models.Model):
self.ensure_one()
res = [(_('Talk Proposals'), '/event/%s/track_proposal' % slug(self), False, 'track_proposal')]
return res
-
- @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
-
- @api.onchange('event_type_id')
- def _onchange_type(self):
- super(Event, self)._onchange_type()
- if self.event_type_id and self.website_menu:
- self.website_track = self.event_type_id.website_track
- self.website_track_proposal = self.event_type_id.website_track_proposal
-
- @api.onchange('website_menu')
- def _onchange_website_menu(self):
- if not self.website_menu:
- self.website_track = False
- self.website_track_proposal = False
-
- @api.onchange('website_track')
- def _onchange_website_track(self):
- if not self.website_track:
- self.website_track_proposal = False
-
- @api.onchange('website_track_proposal')
- def _onchange_website_track_proposal(self):
- if self.website_track_proposal:
- self.website_track = True
diff --git a/addons/website_event_track/models/event_track.py b/addons/website_event_track/models/event_track.py
index bb2ab91999b..80b16d75e16 100644
--- a/addons/website_event_track/models/event_track.py
+++ b/addons/website_event_track/models/event_track.py
@@ -43,10 +43,16 @@ class Track(models.Model):
user_id = fields.Many2one('res.users', 'Responsible', tracking=True, default=lambda self: self.env.user)
company_id = fields.Many2one('res.company', related='event_id.company_id')
partner_id = fields.Many2one('res.partner', 'Speaker')
- partner_name = fields.Char('Name', tracking=10)
- partner_email = fields.Char('Email', tracking=20)
- partner_phone = fields.Char('Phone', tracking=30)
- partner_biography = fields.Html('Biography')
+ partner_name = fields.Char(
+ string='Name', compute='_compute_partner_info',
+ copy=True, readonly=False, store=True, tracking=10)
+ partner_email = fields.Char(
+ string='Email', compute='_compute_partner_info',
+ copy=True, readonly=False, store=True, tracking=20)
+ partner_phone = fields.Char(
+ string='Phone', compute='_compute_partner_info',
+ copy=True, readonly=False, store=True, tracking=30)
+ partner_biography = fields.Html(string='Biography')
tag_ids = fields.Many2many('event.track.tag', string='Tags')
stage_id = fields.Many2one(
'event.track.stage', string='Stage', ondelete='restrict',
@@ -82,12 +88,13 @@ class Track(models.Model):
if track.id:
track.website_url = '/event/%s/track/%s' % (slug(track.event_id), slug(track))
- @api.onchange('partner_id')
- def _onchange_partner_id(self):
- if self.partner_id:
- self.partner_name = self.partner_id.name
- self.partner_email = self.partner_id.email
- self.partner_phone = self.partner_id.phone
+ @api.depends('partner_id')
+ def _compute_partner_info(self):
+ for track in self:
+ if track.partner_id:
+ track.partner_name = track.partner_id.name
+ track.partner_email = track.partner_id.email
+ track.partner_phone = track.partner_id.phone
@api.depends('date', 'duration')
def _compute_end_date(self):
diff --git a/addons/website_event_track/models/website_event_menu.py b/addons/website_event_track/models/website_event_menu.py
new file mode 100644
index 00000000000..89ddc9984d1
--- /dev/null
+++ b/addons/website_event_track/models/website_event_menu.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models
+
+
+class EventMenu(models.Model):
+ _name = "website.event.menu"
+ _description = "Website Event Menu"
+
+ menu_id = fields.Many2one('website.menu', string='Menu', ondelete='cascade')
+ event_id = fields.Many2one('event.event', string='Event', ondelete='cascade')
+ menu_type = fields.Selection([('track', 'Event Tracks Menus'), ('track_proposal', 'Event Proposals Menus')])