[IMP] event: ease event configuration and management through categories
This commit improve event.type (Event Categories) model and views in order to ease event configuration through more detailed categories. The purpose is to be able to define categories holding default data for website, tickets, attendee mailing, ... Choosing a category on a new event takes those default values to help users creating finely-tuned events. Main configuration on event.type is * auto confirmation, replacing the old system-wide auto confirmation parameter * seats limitation * location: online events, timezone * communication: reply-to email address, twitter hashtag, automated mailing of attendees * ticketing * website parameters: display on website, display tracks, allow track proposal * question to attendees
This commit is contained in:
@@ -8,9 +8,6 @@
|
||||
<value>Module Events has been installed</value>
|
||||
</function>
|
||||
|
||||
<!-- Automatic confirmation of events by default -->
|
||||
<function id="default_auto_confirmation" model="ir.values" name="set_default" eval="('event.config.settings', 'default_auto_confirmation', True)"/>
|
||||
|
||||
<!-- Event Mail Scheduler-->
|
||||
<record model="ir.cron" forcecreate="True" id="event_mail_scheduler">
|
||||
<field name="name">Event: Mail Scheduler</field>
|
||||
|
||||
@@ -14,25 +14,8 @@ class EventType(models.Model):
|
||||
_name = 'event.type'
|
||||
_description = 'Event Category'
|
||||
|
||||
name = fields.Char('Event Category', required=True, translate=True)
|
||||
default_reply_to = fields.Char('Reply To')
|
||||
default_registration_min = fields.Integer(
|
||||
'Default Minimum Registration', default=0,
|
||||
help="It will select this default minimum value when you choose this event")
|
||||
default_registration_max = fields.Integer(
|
||||
'Default Maximum Registration', default=0,
|
||||
help="It will select this default maximum value when you choose this event")
|
||||
|
||||
|
||||
class EventEvent(models.Model):
|
||||
"""Event"""
|
||||
_name = 'event.event'
|
||||
_description = 'Event'
|
||||
_inherit = ['mail.thread']
|
||||
_order = 'date_begin'
|
||||
|
||||
@api.model
|
||||
def _default_event_mail_ids(self):
|
||||
def _get_default_event_type_mail_ids(self):
|
||||
return [(0, 0, {
|
||||
'interval_unit': 'now',
|
||||
'interval_type': 'after_sub',
|
||||
@@ -49,6 +32,57 @@ class EventEvent(models.Model):
|
||||
'template_id': self.env.ref('event.event_reminder')
|
||||
})]
|
||||
|
||||
name = fields.Char('Event Category', required=True, translate=True)
|
||||
# registration
|
||||
has_seats_limitation = fields.Boolean(
|
||||
'Limited Seats', default=False)
|
||||
default_registration_min = fields.Integer(
|
||||
'Minimum Registrations', default=0,
|
||||
help="It will select this default minimum value when you choose this event")
|
||||
default_registration_max = fields.Integer(
|
||||
'Maximum Registrations', default=0,
|
||||
help="It will select this default maximum value when you choose this event")
|
||||
auto_confirm = fields.Boolean(
|
||||
'Automatically Confirm Registrations', default=True,
|
||||
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)
|
||||
# communication
|
||||
use_reply_to = fields.Boolean('Use Default Reply-To')
|
||||
default_reply_to = fields.Char('Reply To')
|
||||
use_hashtag = fields.Boolean('Use Default Hashtag')
|
||||
default_hashtag = fields.Char('Twitter Hashtag')
|
||||
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=_get_default_event_type_mail_ids)
|
||||
|
||||
@api.onchange('has_seats_limitation')
|
||||
def _onchange_has_seats_limitation(self):
|
||||
if not self.has_seats_limitation:
|
||||
self.default_registration_min = 0
|
||||
self.default_registration_max = 0
|
||||
|
||||
@api.model
|
||||
def _tz_get(self):
|
||||
return [(x, x) for x in pytz.all_timezones]
|
||||
|
||||
|
||||
class EventEvent(models.Model):
|
||||
"""Event"""
|
||||
_name = 'event.event'
|
||||
_description = 'Event'
|
||||
_inherit = ['mail.thread']
|
||||
_order = 'date_begin'
|
||||
|
||||
name = fields.Char(
|
||||
string='Event Name', translate=True, required=True,
|
||||
readonly=False, states={'done': [('readonly', True)]})
|
||||
@@ -71,7 +105,7 @@ class EventEvent(models.Model):
|
||||
readonly=False, states={'done': [('readonly', True)]},
|
||||
oldname='type')
|
||||
color = fields.Integer('Kanban Color Index')
|
||||
event_mail_ids = fields.One2many('event.mail', 'event_id', string='Mail Schedule', default=_default_event_mail_ids, copy=True)
|
||||
event_mail_ids = fields.One2many('event.mail', 'event_id', string='Mail Schedule', copy=True)
|
||||
|
||||
# Seats and computation
|
||||
seats_max = fields.Integer(
|
||||
@@ -120,16 +154,18 @@ class EventEvent(models.Model):
|
||||
('confirm', 'Confirmed'), ('done', 'Done')],
|
||||
string='Status', default='draft', readonly=True, required=True, copy=False,
|
||||
help="If event is created, the status is 'Draft'. If event is confirmed for the particular dates the status is set to 'Confirmed'. If the event is over, the status is set to 'Done'. If event is cancelled the status is set to 'Cancelled'.")
|
||||
auto_confirm = fields.Boolean(string='Autoconfirm Registrations', compute='_compute_auto_confirm')
|
||||
auto_confirm = fields.Boolean(string='Autoconfirm Registrations')
|
||||
reply_to = fields.Char(
|
||||
'Reply-To Email', readonly=False, states={'done': [('readonly', True)]},
|
||||
help="The email address of the organizer is likely to be put here, with the effect to be in the 'Reply-To' of the mails sent automatically at event or registrations confirmation. You can also put the email address of your mail gateway if you use one.")
|
||||
is_online = fields.Boolean('Online Event')
|
||||
address_id = fields.Many2one(
|
||||
'res.partner', string='Location',
|
||||
default=lambda self: self.env.user.company_id.partner_id,
|
||||
readonly=False, states={'done': [('readonly', True)]},
|
||||
track_visibility="onchange")
|
||||
country_id = fields.Many2one('res.country', 'Country', related='address_id.country_id', store=True)
|
||||
twitter_hashtag = fields.Char('Twitter Hashtag')
|
||||
description = fields.Html(
|
||||
string='Description', oldname='note', translate=html_translate, sanitize_attributes=False,
|
||||
readonly=False, states={'done': [('readonly', True)]})
|
||||
@@ -189,10 +225,6 @@ class EventEvent(models.Model):
|
||||
else:
|
||||
self.date_end_located = False
|
||||
|
||||
@api.one
|
||||
def _compute_auto_confirm(self):
|
||||
self.auto_confirm = self.env['ir.values'].get_default('event.config.settings', 'default_auto_confirmation')
|
||||
|
||||
@api.onchange('event_type_id')
|
||||
def _onchange_type(self):
|
||||
if self.event_type_id:
|
||||
@@ -200,7 +232,28 @@ class EventEvent(models.Model):
|
||||
self.seats_max = self.event_type_id.default_registration_max
|
||||
if self.event_type_id.default_registration_max:
|
||||
self.seats_availability = 'limited'
|
||||
self.reply_to = self.event_type_id.default_reply_to
|
||||
|
||||
if self.event_type_id.auto_confirm:
|
||||
self.auto_confirm = self.event_type_id.auto_confirm
|
||||
|
||||
if self.event_type_id.use_reply_to:
|
||||
self.reply_to = self.event_type_id.default_reply_to
|
||||
|
||||
if self.event_type_id.use_reply_to:
|
||||
self.twitter_hashtag = self.event_type_id.default_hashtag
|
||||
|
||||
if self.event_type_id.use_timezone:
|
||||
self.date_tz = self.event_type_id.default_timezone
|
||||
|
||||
self.is_online = self.event_type_id.is_online
|
||||
|
||||
if self.event_type_id.event_type_mail_ids:
|
||||
self.event_mail_ids = [(5, 0, 0)] + [{
|
||||
'template_id': line.template_id,
|
||||
'interval_nbr': line.interval_nbr,
|
||||
'interval_unit': line.interval_unit,
|
||||
'interval_type': line.interval_type}
|
||||
for line in self.event_type_id.event_type_mail_ids]
|
||||
|
||||
@api.constrains('seats_min', 'seats_max', 'seats_availability')
|
||||
def _check_seats_min_max(self):
|
||||
|
||||
@@ -9,7 +9,6 @@ class event_config_settings(models.TransientModel):
|
||||
module_event_sale = fields.Boolean("Tickets")
|
||||
module_website_event_track = fields.Boolean("Tracks and Agenda")
|
||||
module_website_event_questions = fields.Boolean("Registration Survey")
|
||||
default_auto_confirmation = fields.Boolean("Auto Confirmation", default_model='event.config.settings')
|
||||
module_event_barcode = fields.Boolean("Barcode")
|
||||
module_website_event = fields.Boolean("Online Events")
|
||||
module_website_event_sale = fields.Boolean("Online Ticketing")
|
||||
|
||||
@@ -15,6 +15,32 @@ _INTERVALS = {
|
||||
}
|
||||
|
||||
|
||||
class EventTypeMail(models.Model):
|
||||
""" Template of event.mail to attach to event.type. Those will be copied
|
||||
upon all events created in that type to ease event creation. """
|
||||
_name = 'event.type.mail'
|
||||
_description = 'Mail Scheduling on Event Type'
|
||||
|
||||
event_type_id = fields.Many2one(
|
||||
'event.type', string='Event Type',
|
||||
ondelete='cascade', required=True)
|
||||
interval_nbr = fields.Integer('Interval', default=1)
|
||||
interval_unit = fields.Selection([
|
||||
('now', 'Immediately'),
|
||||
('hours', 'Hour(s)'), ('days', 'Day(s)'),
|
||||
('weeks', 'Week(s)'), ('months', 'Month(s)')],
|
||||
string='Unit', default='hours', required=True)
|
||||
interval_type = fields.Selection([
|
||||
('after_sub', 'After each registration'),
|
||||
('before_event', 'Before the event'),
|
||||
('after_event', 'After the event')],
|
||||
string='Trigger', default="before_event", required=True)
|
||||
template_id = fields.Many2one(
|
||||
'mail.template', string='Email Template',
|
||||
domain=[('model', '=', 'event.registration')], required=True, ondelete='restrict',
|
||||
help='This field contains the template of the mail that will be automatically sent')
|
||||
|
||||
|
||||
class EventMailScheduler(models.Model):
|
||||
""" Event automated mailing. This model replaces all existing fields and
|
||||
configuration allowing to send emails on events since Odoo 9. A cron exists
|
||||
@@ -37,7 +63,7 @@ class EventMailScheduler(models.Model):
|
||||
('after_event', 'After the event')],
|
||||
string='Trigger ', default="before_event", required=True)
|
||||
template_id = fields.Many2one(
|
||||
'mail.template', string='Email to Send',
|
||||
'mail.template', string='Email Template',
|
||||
domain=[('model', '=', 'event.registration')], required=True, ondelete='restrict',
|
||||
help='This field contains the template of the mail that will be automatically sent')
|
||||
scheduled_date = fields.Datetime('Scheduled Sent Mail', compute='_compute_scheduled_date', store=True)
|
||||
|
||||
@@ -10,3 +10,5 @@ access_event_mail,event.mail,model_event_mail,event.group_event_user,1,0,0,0
|
||||
access_event_mail_manager,event.mail manager,model_event_mail,event.group_event_manager,1,1,1,1
|
||||
access_event_mail_registration,event.mail.registration,model_event_mail_registration,event.group_event_user,1,0,0,0
|
||||
access_event_mail_registration_manager,event.mail.registration.manager,model_event_mail_registration,event.group_event_manager,1,1,1,1
|
||||
access_event_type_mail_event_user,event.type.mail.event.user,model_event_type_mail,event.group_event_user,1,0,0,0
|
||||
access_event_type_mail_event_manager,event.type.mail.event.manager,model_event_type_mail,event.group_event_manager,1,1,1,1
|
||||
|
||||
|
@@ -15,14 +15,10 @@ class TestEventFlow(TestEventCommon):
|
||||
@mute_logger('odoo.addons.base.ir.ir_model', 'odoo.models')
|
||||
def test_00_basic_event_auto_confirm(self):
|
||||
""" Basic event management with auto confirmation """
|
||||
event_config = self.env['event.config.settings'].sudo(self.user_eventmanager).create({
|
||||
'default_auto_confirmation': 1
|
||||
})
|
||||
event_config.execute()
|
||||
|
||||
# EventUser creates a new event: ok
|
||||
test_event = self.Event.sudo(self.user_eventmanager).create({
|
||||
'name': 'TestEvent',
|
||||
'auto_confirm': True,
|
||||
'date_begin': datetime.datetime.now() + relativedelta(days=-1),
|
||||
'date_end': datetime.datetime.now() + relativedelta(days=1),
|
||||
'seats_max': 2,
|
||||
@@ -66,13 +62,10 @@ class TestEventFlow(TestEventCommon):
|
||||
with self.assertRaises(UserError):
|
||||
test_event.button_cancel()
|
||||
|
||||
|
||||
@mute_logger('odoo.addons.base.ir.ir_model', 'odoo.models')
|
||||
def test_10_advanced_event_flow(self):
|
||||
""" Avanced event flow: no auto confirmation, manage minimum / maximum
|
||||
seats, ... """
|
||||
self.env['ir.values'].set_default('event.config.settings', 'default_auto_confirmation', False)
|
||||
|
||||
# EventUser creates a new event: ok
|
||||
test_event = self.Event.sudo(self.user_eventmanager).create({
|
||||
'name': 'TestEvent',
|
||||
@@ -114,7 +107,6 @@ class TestEventFlow(TestEventCommon):
|
||||
]})
|
||||
with self.assertRaises(AccessError):
|
||||
event_config = self.env['event.config.settings'].sudo(self.user_eventmanager).create({
|
||||
'default_auto_confirmation': 1
|
||||
})
|
||||
event_config.execute()
|
||||
|
||||
|
||||
@@ -13,13 +13,13 @@ class TestMailSchedule(TestEventCommon):
|
||||
@mute_logger('odoo.addons.base.ir.ir_model', 'odoo.models')
|
||||
def test_00_event_mail_schedule(self):
|
||||
""" Test mail scheduling for events """
|
||||
self.env['ir.values'].set_default('event.config.settings', 'default_auto_confirmation', True)
|
||||
now = fields.datetime.now()
|
||||
event_date_begin = now + relativedelta(days=1)
|
||||
event_date_end = now + relativedelta(days=3)
|
||||
|
||||
test_event = self.Event.sudo(self.user_eventmanager).create({
|
||||
'name': 'TestEventMail',
|
||||
'auto_confirm': True,
|
||||
'date_begin': event_date_begin,
|
||||
'date_end': event_date_end,
|
||||
'seats_max': 10,
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6 o_setting_box">
|
||||
<div class="col-xs-12 col-md-6 o_setting_box" name="event_settings_website">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="module_website_event_sale"/>
|
||||
</div>
|
||||
@@ -60,17 +60,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6 o_setting_box" id="default_auto_confirmation" title="Uncheck this option to manually manage draft event and draft registration.">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="default_auto_confirmation"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="default_auto_confirmation"/>
|
||||
<div class="text-muted">
|
||||
Confirm registrations automatically
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Attendance</h2>
|
||||
<div class="row mt16 o_settings_container">
|
||||
|
||||
@@ -12,13 +12,134 @@
|
||||
<field name="model">event.type</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Event Category">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="default_reply_to"/>
|
||||
<field name="default_registration_min"/>
|
||||
<field name="default_registration_max"/>
|
||||
</group>
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name"/></h1>
|
||||
</div>
|
||||
<h2>Location</h2>
|
||||
<div class="row mt16 o_settings_container" name="event_type_location">
|
||||
<div class="col-xs-12 col-md-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-md-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-xs-12 col-md-6 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="use_timezone"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="use_timezone"/>
|
||||
<div class="row">
|
||||
<div class="col-md-8 mt16" attrs="{'invisible': [('use_timezone', '=', False)]}">
|
||||
<label for="default_timezone"/>
|
||||
<field name="default_timezone"
|
||||
attrs="{'required': [('use_timezone', '=', True)]}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Communication</h2>
|
||||
<div class="row mt16 o_settings_container" name="event_type_communication">
|
||||
<div class="col-xs-12 col-md-6 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="use_reply_to"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="use_reply_to"/>
|
||||
<div class="row">
|
||||
<div class="col-md-8 mt16" attrs="{'invisible': [('use_reply_to', '=', False)]}">
|
||||
<label for="default_reply_to"/>
|
||||
<field name="default_reply_to"
|
||||
attrs="{'required': [('use_reply_to', '=', True)]}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="use_hashtag"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="use_hashtag"/>
|
||||
<div class="row">
|
||||
<div class="col-md-8 mt16" attrs="{'invisible': [('use_hashtag', '=', False)]}">
|
||||
<label for="default_hashtag"/>
|
||||
<field name="default_hashtag"
|
||||
attrs="{'required': [('use_hashtag', '=', True)]}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-12 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="use_mail_schedule"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="use_mail_schedule"/>
|
||||
<div class="row mt16" attrs="{'invisible': [('use_mail_schedule', '=', False)]}">
|
||||
<div class="col-md-9">
|
||||
<field name="event_type_mail_ids">
|
||||
<tree string="Email Schedule" editable="bottom">
|
||||
<field name="template_id"/>
|
||||
<field name="interval_nbr" attrs="{'readonly':[('interval_unit', '=', 'now')]}"/>
|
||||
<field name="interval_unit"/>
|
||||
<field name="interval_type"/>
|
||||
</tree>
|
||||
</field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Visibility</h2>
|
||||
<div class="row mt16 o_settings_container" name="event_type_visibility">
|
||||
<div class="col-xs-12 col-md-6 o_setting_box" name="event_type_visibility_seats">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="has_seats_limitation"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="has_seats_limitation"/>
|
||||
<div class="row">
|
||||
<div class="col-md-8 mt16" attrs="{'invisible': [('has_seats_limitation', '=', False)]}">
|
||||
<div>
|
||||
<label for="default_registration_min"/> <field name="default_registration_min"/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="default_registration_max"/> <field name="default_registration_max"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Attendees</h2>
|
||||
<div class="row mt16 o_settings_container" name="event_type_attendees">
|
||||
<div class="col-xs-12 col-md-6 o_setting_box" name="event_type_attendees_auto_confirm">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="auto_confirm"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="auto_confirm"/>
|
||||
<div class="row">
|
||||
<div class="col-md-8 mt16 text-muted">
|
||||
Events and registrations will automatically be confirmed
|
||||
upon creation, easing the flow for simple events.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
@@ -93,17 +214,23 @@
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name" placeholder="Event Name"/></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="address_id" context="{'show_address': 1}" options='{"always_reload": True}'/>
|
||||
<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"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="event_type_id" options="{'no_create':True}"/>
|
||||
<field name="reply_to" string="Contact Email"/>
|
||||
<field name="twitter_hashtag"/>
|
||||
<field name="auto_confirm" groups="base.group_no_one"/>
|
||||
</group>
|
||||
<group>
|
||||
|
||||
@@ -2,26 +2,56 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.exceptions import ValidationError, UserError
|
||||
|
||||
import odoo.addons.decimal_precision as dp
|
||||
|
||||
|
||||
class EventType(models.Model):
|
||||
_inherit = 'event.type'
|
||||
|
||||
@api.model
|
||||
def _get_default_event_ticket_ids(self):
|
||||
product = self.env.ref('event_sale.product_product_event', raise_if_not_found=False)
|
||||
if not product:
|
||||
return False
|
||||
return [(0, 0, {
|
||||
'name': _('Registration'),
|
||||
'product_id': product.id,
|
||||
'price': 0,
|
||||
})]
|
||||
|
||||
use_ticketing = fields.Boolean('Ticketing')
|
||||
event_ticket_ids = fields.One2many(
|
||||
'event.event.ticket', 'event_type_id',
|
||||
string='Tickets', default=_get_default_event_ticket_ids)
|
||||
|
||||
@api.onchange('name')
|
||||
def _onchange_name(self):
|
||||
if self.name:
|
||||
self.event_ticket_ids.filtered(lambda ticket: ticket.name == _('Registration')).update({
|
||||
'name': _('Registration for %s') % self.name
|
||||
})
|
||||
|
||||
|
||||
class Event(models.Model):
|
||||
_inherit = 'event.event'
|
||||
|
||||
def _default_tickets(self):
|
||||
product = self.env.ref('event_sale.product_product_event', raise_if_not_found=False)
|
||||
if not product:
|
||||
return self.env['event.event.ticket']
|
||||
return [{
|
||||
'name': _('Registration'),
|
||||
'product_id': product.id,
|
||||
'price': 0,
|
||||
}]
|
||||
event_ticket_ids = fields.One2many(
|
||||
'event.event.ticket', 'event_id', string='Event Ticket',
|
||||
copy=True)
|
||||
|
||||
event_ticket_ids = fields.One2many('event.event.ticket', 'event_id', string='Event Ticket',
|
||||
default=lambda self: self._default_tickets(), copy=True)
|
||||
@api.onchange('event_type_id')
|
||||
def _onchange_type(self):
|
||||
super(Event, self)._onchange_type()
|
||||
if self.event_type_id.use_ticketing:
|
||||
self.event_ticket_ids = [(5, 0, 0)] + [
|
||||
(0, 0, {
|
||||
'name': self.name and _('Registration for %s') % self.name or ticket.name,
|
||||
'product_id': ticket.product_id.id,
|
||||
'price': ticket.price,
|
||||
})
|
||||
for ticket in self.event_type_id.event_ticket_ids]
|
||||
|
||||
|
||||
class EventTicket(models.Model):
|
||||
@@ -32,7 +62,8 @@ class EventTicket(models.Model):
|
||||
return self.env.ref('event_sale.product_product_event', raise_if_not_found=False)
|
||||
|
||||
name = fields.Char(string='Name', required=True, translate=True)
|
||||
event_id = fields.Many2one('event.event', string="Event", required=True, ondelete='cascade')
|
||||
event_type_id = fields.Many2one('event.type', string='Event Category', ondelete='cascade')
|
||||
event_id = fields.Many2one('event.event', string="Event", ondelete='cascade')
|
||||
product_id = fields.Many2one('product.product', string='Product',
|
||||
required=True, domain=[("event_ok", "=", True)],
|
||||
default=_default_product_id)
|
||||
@@ -113,6 +144,11 @@ class EventTicket(models.Model):
|
||||
if record.seats_max and record.seats_available < 0:
|
||||
raise ValidationError(_('No more available seats for the ticket'))
|
||||
|
||||
@api.constrains('event_type_id', 'event_id')
|
||||
def _constrains_event(self):
|
||||
if any(ticket.event_type_id and ticket.event_id for ticket in self):
|
||||
raise UserError(_('Ticket should belong to either event category or event but not both'))
|
||||
|
||||
@api.onchange('product_id')
|
||||
def _onchange_product_id(self):
|
||||
self.price = self.product_id.list_price or 0
|
||||
|
||||
@@ -41,6 +41,35 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="event_type_view_form_inherit_sale" model="ir.ui.view">
|
||||
<field name="name">event.type.view.form.inherit.sale</field>
|
||||
<field name="model">event.type</field>
|
||||
<field name="inherit_id" ref="event.view_event_type_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<div name="event_type_attendees_auto_confirm" position="after">
|
||||
<div class="col-xs-12 col-md-12 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="use_ticketing"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="use_ticketing"/>
|
||||
<div class="row mt16" attrs="{'invisible': [('use_ticketing', '=', False)]}">
|
||||
<div class="col-md-9">
|
||||
<field name="event_ticket_ids">
|
||||
<tree string="Tickets" editable="bottom">
|
||||
<field name="name"/>
|
||||
<field name="product_id"/>
|
||||
<field name="price"/>
|
||||
</tree>
|
||||
</field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_event_form_inherit_ticket" model="ir.ui.view">
|
||||
<field name="name">event.form.inherit</field>
|
||||
<field name="model">event.event</field>
|
||||
|
||||
@@ -6,14 +6,18 @@ from odoo import api, fields, models, _
|
||||
from odoo.addons.website.models.website import slug
|
||||
|
||||
|
||||
class EventType(models.Model):
|
||||
_name = 'event.type'
|
||||
_inherit = ['event.type']
|
||||
|
||||
website_menu = fields.Boolean(
|
||||
'Display a dedicated menu on Website')
|
||||
|
||||
|
||||
class Event(models.Model):
|
||||
_name = 'event.event'
|
||||
_inherit = ['event.event', 'website.seo.metadata', 'website.published.mixin']
|
||||
|
||||
def _default_hashtag(self):
|
||||
return re.sub("[- \\.\\(\\)\\@\\#\\&]+", "", self.env.user.company_id.name).lower()
|
||||
|
||||
twitter_hashtag = fields.Char('Twitter Hashtag', default=_default_hashtag)
|
||||
website_published = fields.Boolean(track_visibility='onchange')
|
||||
website_message_ids = fields.One2many(
|
||||
'mail.message', 'res_id',
|
||||
@@ -47,6 +51,12 @@ 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
|
||||
|
||||
def _get_menu_entries(self):
|
||||
self.ensure_one()
|
||||
return [
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="event_config_settings_view_form_inherit_website_event" model="ir.ui.view">
|
||||
<field name="name">event.config.settings.form.inherit.website.event</field>
|
||||
<record id="event_config_settings_view_form_inherit_website" model="ir.ui.view">
|
||||
<field name="name">event.config.settings.view.form.inherit.website</field>
|
||||
<field name="model">event.config.settings</field>
|
||||
<field name="inherit_id" ref="event.event_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<div id="default_auto_confirmation" position="before">
|
||||
<div name="event_settings_website" position="after">
|
||||
<div class="col-xs-12 col-md-6 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="module_website_event_questions"/>
|
||||
|
||||
@@ -1,8 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="view_event_sale_form">
|
||||
<field name="name">event.event.website.form</field>
|
||||
<record id="event_type_view_form_inherit_website" model="ir.ui.view">
|
||||
<field name="name">event.type.view.form.inherit.website</field>
|
||||
<field name="model">event.type</field>
|
||||
<field name="inherit_id" ref="event.view_event_type_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<div name="event_type_visibility_seats" position="after">
|
||||
<div class="col-xs-12 col-md-6 o_setting_box" name="event_type_visibility_website">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="website_menu"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="website_menu"/>
|
||||
<div class="row">
|
||||
<div class="col-md-8 mt16 text-muted">
|
||||
Check this option to have menus for your event on the
|
||||
website: registrations, schedule, map, ...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="event_event_view_form_inherit_website" model="ir.ui.view">
|
||||
<field name="name">event.event.view.form.inherit.website</field>
|
||||
<field name="model">event.event</field>
|
||||
<field name="inherit_id" ref="event.view_event_form"/>
|
||||
<field name="arch" type="xml">
|
||||
@@ -15,9 +39,10 @@
|
||||
<field name="website_published" widget="website_button"/>
|
||||
</button>
|
||||
</button>
|
||||
<xpath expr="//field[@name='organizer_id']" position="after">
|
||||
<field name="twitter_hashtag"/>
|
||||
</xpath>
|
||||
<label for="is_online" position="after">
|
||||
<field name="website_menu"/>
|
||||
<label for="website_menu" string="Website Menu"/>
|
||||
</label>
|
||||
<xpath expr="//button[@name='button_done']" position="before">
|
||||
<button name="action_open_badge_editor"
|
||||
type="object"
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class EventType(models.Model):
|
||||
_inherit = 'event.type'
|
||||
|
||||
use_questions = fields.Boolean('Questions to Attendees')
|
||||
question_ids = fields.One2many(
|
||||
'event.question', 'event_type_id',
|
||||
string='Questions', copy=True)
|
||||
|
||||
|
||||
class EventEvent(models.Model):
|
||||
@@ -14,6 +24,23 @@ class EventEvent(models.Model):
|
||||
specific_question_ids = fields.One2many('event.question', 'event_id', 'Specific Questions',
|
||||
domain=[('is_individual', '=', True)])
|
||||
|
||||
@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,
|
||||
'is_individual': question.is_individual,
|
||||
'answer_ids': [(0, 0, {
|
||||
'name': answer.name,
|
||||
'sequence': answer.sequence,
|
||||
}) for answer in question.answer_ids]
|
||||
})
|
||||
for question in self.event_type_id.question_ids
|
||||
]
|
||||
|
||||
|
||||
class EventRegistrationAnswer(models.Model):
|
||||
''' This m2m table has to be explicitly instanciated as we need unique ids
|
||||
@@ -41,13 +68,19 @@ class EventQuestion(models.Model):
|
||||
_order = 'sequence,id'
|
||||
|
||||
title = fields.Char(required=True, translate=True)
|
||||
event_id = fields.Many2one('event.event', required=True, ondelete='cascade')
|
||||
event_type_id = fields.Many2one('event.type', 'Event Type', ondelete='cascade')
|
||||
event_id = fields.Many2one('event.event', 'Event', ondelete='cascade')
|
||||
answer_ids = fields.One2many('event.answer', 'question_id', "Answers", required=True, copy=True)
|
||||
sequence = fields.Integer(default=10)
|
||||
is_individual = fields.Boolean('Ask each attendee',
|
||||
help="If True, this question will be asked for every attendee of a reservation. If "
|
||||
"not it will be asked only once and its value propagated to every attendees.")
|
||||
|
||||
@api.constrains('event_type_id', 'event_id')
|
||||
def _constrains_event(self):
|
||||
if any(question.event_type_id and question.event_id for question in self):
|
||||
raise UserError(_('Question should belong to either event category or event but not both'))
|
||||
|
||||
|
||||
class EventAnswer(models.Model):
|
||||
_name = 'event.answer'
|
||||
|
||||
@@ -31,10 +31,33 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="event_type_view_form_inherit_question" model="ir.ui.view">
|
||||
<field name="name">event.type.view.form.inherit.question</field>
|
||||
<field name="model">event.type</field>
|
||||
<field name="inherit_id" ref="website_event.event_type_view_form_inherit_website"/>
|
||||
<field name="arch" type="xml">
|
||||
<div name="event_type_attendees_auto_confirm" position="after">
|
||||
<div class="col-xs-12 col-md-12 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="use_questions"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="use_questions"/>
|
||||
<div class="row mt16" attrs="{'invisible': [('use_ticketing', '=', False)]}">
|
||||
<div class="col-md-9">
|
||||
<field name="question_ids"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_event_form_inherit_question">
|
||||
<field name="name">event.form.inherit.question</field>
|
||||
<field name="name">event.event.view.form.inherit.question</field>
|
||||
<field name="model">event.event</field>
|
||||
<field name="inherit_id" ref="event.view_event_form"/>
|
||||
<field name="inherit_id" ref="website_event.event_event_view_form_inherit_website"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//notebook" position="inside">
|
||||
|
||||
@@ -5,6 +5,13 @@ from odoo import api, fields, models, _
|
||||
from odoo.addons.website.models.website 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')
|
||||
|
||||
|
||||
class Event(models.Model):
|
||||
_inherit = "event.event"
|
||||
|
||||
@@ -54,6 +61,13 @@ class Event(models.Model):
|
||||
for event in self:
|
||||
event.tracks_tag_ids = event.track_ids.mapped('tag_ids').ids
|
||||
|
||||
@api.onchange('event_type_id')
|
||||
def _onchange_type(self):
|
||||
super(Event, self)._onchange_type()
|
||||
if self.event_type_id:
|
||||
self.website_track = self.event_type_id.website_track
|
||||
self.website_track_proposal = self.event_type_id.website_track_proposal
|
||||
|
||||
def _get_menu_entries(self):
|
||||
self.ensure_one()
|
||||
res = super(Event, self)._get_menu_entries()
|
||||
|
||||
@@ -1,9 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="event_type_view_form_inherit_track" model="ir.ui.view">
|
||||
<field name="name">event.type.view.form.inherit.track</field>
|
||||
<field name="model">event.type</field>
|
||||
<field name="inherit_id" ref="website_event.event_type_view_form_inherit_website"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='event_type_visibility_website']/div[@class='o_setting_right_pane']" position='inside'>
|
||||
<div class="row mt16">
|
||||
<label class="col-md-4" for="website_track"/> <field name="website_track"/>
|
||||
</div>
|
||||
<div class="row mt16">
|
||||
<label class="col-md-4" for="website_track_proposal"/> <field name="website_track_proposal"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Events Organisation/CONFIGURATION/EVENTS -->
|
||||
<record id="view_event_form" model="ir.ui.view">
|
||||
<field name="name">Event Tracks</field>
|
||||
<field name="inherit_id" ref="event.view_event_form"/>
|
||||
<field name="inherit_id" ref="website_event.event_event_view_form_inherit_website"/>
|
||||
<field name="model">event.event</field>
|
||||
<field name="arch" type="xml">
|
||||
<button name="website_publish_button" position="before">
|
||||
@@ -20,16 +37,12 @@
|
||||
<field name="sponsor_count" widget="statinfo"/>
|
||||
</button>
|
||||
</button>
|
||||
<xpath expr="//div[@class='oe_title']" position="inside">
|
||||
<div name="event_checkboxes">
|
||||
<field name="website_menu"/>
|
||||
<label for="website_menu"/>
|
||||
<field name="website_track" attrs="{'invisible': [('website_menu', '=', False)]}"/>
|
||||
<label for="website_track" attrs="{'invisible': [('website_menu', '=', False)]}"/>
|
||||
<field name="website_track_proposal" attrs="{'invisible': [('website_menu', '=', False)]}"/>
|
||||
<label for="website_track_proposal" attrs="{'invisible': [('website_menu', '=', False)]}"/>
|
||||
</div>
|
||||
</xpath>
|
||||
<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>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
Reference in New Issue
Block a user