diff options
| author | Thibault Delavallée <tde@odoo.com> | 2020-01-09 14:29:26 +0000 |
|---|---|---|
| committer | Thibault Delavallée <tde@odoo.com> | 2020-01-10 16:06:40 +0000 |
| commit | abcd6c15cf27d0e5da77b2e08a742f40e11c4e84 (patch) | |
| tree | 123ce41b18972f5cd4ad65a24215499fe4877866 /addons/event | |
| parent | 59e5c8630fef744de7d1f4cdd648edd809507c6d (diff) | |
[MOV] (website_)event(_sale/_question/_track): split main model files
PURPOSE
As event will soon evolve (onchange -> compute, code improvements, addition of
new features) cleaning and improving tests is necessary to help avoid issues.
SPECIFICATIONS
As event model grow in complexity and features, it is easier to find its
way through the application with having registration model lying in its
own file to separate it from event-specific models (event.type, event.event).
Ticket (event_sale) and sponsor (website_event_track) models are also extracted
in their own file.
LINKS
LINKS
Side effect of Task ID 2089156 (event onchange to compute)
Community PR odoo/odoo#43127
Enterprise PR odoo/enterprise#7656
Diffstat (limited to 'addons/event')
| -rw-r--r-- | addons/event/models/__init__.py | 3 | ||||
| -rw-r--r-- | addons/event/models/event_event.py (renamed from addons/event/models/event.py) | 198 | ||||
| -rw-r--r-- | addons/event/models/event_mail.py | 1 | ||||
| -rw-r--r-- | addons/event/models/event_registration.py | 202 | ||||
| -rw-r--r-- | addons/event/models/event_stage.py | 1 | ||||
| -rw-r--r-- | addons/event/tests/test_event_internals.py | 2 |
6 files changed, 208 insertions, 199 deletions
diff --git a/addons/event/models/__init__.py b/addons/event/models/__init__.py index 750d7b5de79..727b34645d7 100644 --- a/addons/event/models/__init__.py +++ b/addons/event/models/__init__.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import event +from . import event_event from . import event_mail +from . import event_registration from . import event_stage from . import res_config_settings from . import res_partner diff --git a/addons/event/models/event.py b/addons/event/models/event_event.py index d49b79d369a..4f366871155 100644 --- a/addons/event/models/event.py +++ b/addons/event/models/event_event.py @@ -1,16 +1,15 @@ # -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. import logging import pytz from odoo import _, api, fields, models from odoo.tools import format_datetime -from odoo.exceptions import AccessError, UserError, ValidationError +from odoo.exceptions import ValidationError from odoo.tools.translate import html_translate from odoo.tools.misc import _format_time_ago -from dateutil.relativedelta import relativedelta - _logger = logging.getLogger(__name__) try: @@ -395,196 +394,3 @@ class EventEvent(models.Model): result[event.id] = cal.serialize().encode('utf-8') return result - - -class EventRegistration(models.Model): - _name = 'event.registration' - _description = 'Event Registration' - _inherit = ['mail.thread', 'mail.activity.mixin'] - _order = 'id desc' - - # event - origin = fields.Char( - string='Source Document', readonly=True, - help="Reference of the document that created the registration, for example a sales order") - event_id = fields.Many2one( - 'event.event', string='Event', required=True, - readonly=True, states={'draft': [('readonly', False)]}) - # attendee - partner_id = fields.Many2one( - 'res.partner', string='Contact', - states={'done': [('readonly', True)]}) - name = fields.Char(string='Attendee Name', index=True) - email = fields.Char(string='Email') - phone = fields.Char(string='Phone') - mobile = fields.Char(string='Mobile') - # 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) - 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( - 'res.company', string='Company', related='event_id.company_id', - store=True, readonly=True, states={'draft': [('readonly', False)]}) - state = fields.Selection([ - ('draft', 'Unconfirmed'), ('cancel', 'Cancelled'), - ('open', 'Confirmed'), ('done', 'Attended')], - string='Status', default='draft', readonly=True, copy=False, tracking=True) - - @api.constrains('event_id', 'state') - def _check_seats_limit(self): - for registration in self: - if registration.event_id.seats_availability == 'limited' and registration.event_id.seats_max and registration.event_id.seats_available < (1 if registration.state == 'draft' else 0): - raise ValidationError(_('No more seats available for this event.')) - - def _check_auto_confirmation(self): - if self._context.get('registration_force_draft'): - return False - if any(not registration.event_id.auto_confirm or - (not registration.event_id.seats_available and registration.event_id.seats_availability == 'limited') for registration in self): - return False - return True - - @api.model - def create(self, vals): - registration = super(EventRegistration, self).create(vals) - if registration._check_auto_confirmation(): - registration.sudo().confirm_registration() - return registration - - @api.model - def _prepare_attendee_values(self, registration): - """ Method preparing the values to create new attendees based on a - sales order line. It takes some registration data (dict-based) that are - optional values coming from an external input like a web page. This method - is meant to be inherited in various addons that sell events. """ - partner_id = registration.pop('partner_id', self.env.user.partner_id) - event_id = registration.pop('event_id', False) - data = { - 'name': registration.get('name', partner_id.name), - 'phone': registration.get('phone', partner_id.phone), - 'mobile': registration.get('mobile', partner_id.mobile), - 'email': registration.get('email', partner_id.email), - 'partner_id': partner_id.id, - 'event_id': event_id and event_id.id or False, - } - data.update({key: value for key, value in registration.items() if key in self._fields}) - return data - - def do_draft(self): - self.write({'state': 'draft'}) - - def confirm_registration(self): - self.write({'state': 'open'}) - - # auto-trigger after_sub (on subscribe) mail schedulers, if needed - onsubscribe_schedulers = self.event_id.event_mail_ids.filtered( - lambda s: s.interval_type == 'after_sub') - onsubscribe_schedulers.execute() - - def button_reg_close(self): - """ Close Registration """ - for registration in self: - registration.write({'state': 'done', 'date_closed': fields.Datetime.now()}) - - def button_reg_cancel(self): - self.write({'state': 'cancel'}) - - @api.onchange('partner_id') - def _onchange_partner(self): - if self.partner_id: - contact_id = self.partner_id.address_get().get('contact', False) - if contact_id: - contact = self.env['res.partner'].browse(contact_id) - self.name = contact.name or self.name - self.email = contact.email or self.email - self.phone = contact.phone or self.phone - self.mobile = contact.mobile or self.mobile - - def _message_get_suggested_recipients(self): - recipients = super(EventRegistration, self)._message_get_suggested_recipients() - public_users = self.env['res.users'].sudo() - public_groups = self.env.ref("base.group_public", raise_if_not_found=False) - if public_groups: - public_users = public_groups.sudo().with_context(active_test=False).mapped("users") - try: - for attendee in self: - is_public = attendee.sudo().with_context(active_test=False).partner_id.user_ids in public_users if public_users else False - if attendee.partner_id and not is_public: - attendee._message_add_suggested_recipient(recipients, partner=attendee.partner_id, reason=_('Customer')) - elif attendee.email: - attendee._message_add_suggested_recipient(recipients, email=attendee.email, reason=_('Customer Email')) - except AccessError: # no read access rights -> ignore suggested recipients - pass - return recipients - - def _message_get_default_recipients(self): - # Prioritize registration email over partner_id, which may be shared when a single - # partner booked multiple seats - return {r.id: { - 'partner_ids': [], - 'email_to': r.email, - 'email_cc': False} - for r in self} - - def _message_post_after_hook(self, message, msg_vals): - if self.email and not self.partner_id: - # we consider that posting a message with a specified recipient (not a follower, a specific one) - # on a document without customer means that it was created through the chatter using - # suggested recipients. This heuristic allows to avoid ugly hacks in JS. - new_partner = message.partner_ids.filtered(lambda partner: partner.email == self.email) - if new_partner: - self.search([ - ('partner_id', '=', False), - ('email', '=', new_partner.email), - ('state', 'not in', ['cancel']), - ]).write({'partner_id': new_partner.id}) - return super(EventRegistration, self)._message_post_after_hook(message, msg_vals) - - def action_send_badge_email(self): - """ Open a window to compose an email, with the template - 'event_badge' - message loaded by default - """ - self.ensure_one() - template = self.env.ref('event.event_registration_mail_template_badge') - compose_form = self.env.ref('mail.email_compose_message_wizard_form') - ctx = dict( - default_model='event.registration', - default_res_id=self.id, - default_use_template=bool(template), - default_template_id=template.id, - default_composition_mode='comment', - custom_layout="mail.mail_notification_light", - ) - return { - 'name': _('Compose Email'), - 'type': 'ir.actions.act_window', - 'view_mode': 'form', - 'res_model': 'mail.compose.message', - 'views': [(compose_form.id, 'form')], - 'view_id': compose_form.id, - 'target': 'new', - 'context': ctx, - } - - def get_date_range_str(self): - self.ensure_one() - today = fields.Datetime.now() - event_date = self.event_begin_date - diff = (event_date.date() - today.date()) - if diff.days <= 0: - return _('today') - elif diff.days == 1: - return _('tomorrow') - elif (diff.days < 7): - return _('in %d days') % (diff.days, ) - elif (diff.days < 14): - return _('next week') - elif event_date.month == (today + relativedelta(months=+1)).month: - return _('next month') - else: - return _('on ') + format_datetime(self.env, self.event_begin_date, tz=self.event_id.date_tz, dt_format='medium') - - def summary(self): - self.ensure_one() - return {'information': []} diff --git a/addons/event/models/event_mail.py b/addons/event/models/event_mail.py index fd53adbb0bc..4f2407609f9 100644 --- a/addons/event/models/event_mail.py +++ b/addons/event/models/event_mail.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. from datetime import datetime from dateutil.relativedelta import relativedelta diff --git a/addons/event/models/event_registration.py b/addons/event/models/event_registration.py new file mode 100644 index 00000000000..8f940a2812e --- /dev/null +++ b/addons/event/models/event_registration.py @@ -0,0 +1,202 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + + +from odoo import _, api, fields, models +from odoo.tools import format_datetime +from odoo.exceptions import AccessError, ValidationError + +from dateutil.relativedelta import relativedelta + + +class EventRegistration(models.Model): + _name = 'event.registration' + _description = 'Event Registration' + _inherit = ['mail.thread', 'mail.activity.mixin'] + _order = 'id desc' + + # event + origin = fields.Char( + string='Source Document', readonly=True, + help="Reference of the document that created the registration, for example a sales order") + event_id = fields.Many2one( + 'event.event', string='Event', required=True, + readonly=True, states={'draft': [('readonly', False)]}) + # attendee + partner_id = fields.Many2one( + 'res.partner', string='Contact', + states={'done': [('readonly', True)]}) + name = fields.Char(string='Attendee Name', index=True) + email = fields.Char(string='Email') + phone = fields.Char(string='Phone') + mobile = fields.Char(string='Mobile') + # 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) + 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( + 'res.company', string='Company', related='event_id.company_id', + store=True, readonly=True, states={'draft': [('readonly', False)]}) + state = fields.Selection([ + ('draft', 'Unconfirmed'), ('cancel', 'Cancelled'), + ('open', 'Confirmed'), ('done', 'Attended')], + string='Status', default='draft', readonly=True, copy=False, tracking=True) + + @api.constrains('event_id', 'state') + def _check_seats_limit(self): + for registration in self: + if registration.event_id.seats_availability == 'limited' and registration.event_id.seats_max and registration.event_id.seats_available < (1 if registration.state == 'draft' else 0): + raise ValidationError(_('No more seats available for this event.')) + + def _check_auto_confirmation(self): + if self._context.get('registration_force_draft'): + return False + if any(not registration.event_id.auto_confirm or + (not registration.event_id.seats_available and registration.event_id.seats_availability == 'limited') for registration in self): + return False + return True + + @api.model + def create(self, vals): + registration = super(EventRegistration, self).create(vals) + if registration._check_auto_confirmation(): + registration.sudo().confirm_registration() + return registration + + @api.model + def _prepare_attendee_values(self, registration): + """ Method preparing the values to create new attendees based on a + sales order line. It takes some registration data (dict-based) that are + optional values coming from an external input like a web page. This method + is meant to be inherited in various addons that sell events. """ + partner_id = registration.pop('partner_id', self.env.user.partner_id) + event_id = registration.pop('event_id', False) + data = { + 'name': registration.get('name', partner_id.name), + 'phone': registration.get('phone', partner_id.phone), + 'mobile': registration.get('mobile', partner_id.mobile), + 'email': registration.get('email', partner_id.email), + 'partner_id': partner_id.id, + 'event_id': event_id and event_id.id or False, + } + data.update({key: value for key, value in registration.items() if key in self._fields}) + return data + + def do_draft(self): + self.write({'state': 'draft'}) + + def confirm_registration(self): + self.write({'state': 'open'}) + + # auto-trigger after_sub (on subscribe) mail schedulers, if needed + onsubscribe_schedulers = self.event_id.event_mail_ids.filtered( + lambda s: s.interval_type == 'after_sub') + onsubscribe_schedulers.execute() + + def button_reg_close(self): + """ Close Registration """ + for registration in self: + registration.write({'state': 'done', 'date_closed': fields.Datetime.now()}) + + def button_reg_cancel(self): + self.write({'state': 'cancel'}) + + @api.onchange('partner_id') + def _onchange_partner(self): + if self.partner_id: + contact_id = self.partner_id.address_get().get('contact', False) + if contact_id: + contact = self.env['res.partner'].browse(contact_id) + self.name = contact.name or self.name + self.email = contact.email or self.email + self.phone = contact.phone or self.phone + self.mobile = contact.mobile or self.mobile + + def _message_get_suggested_recipients(self): + recipients = super(EventRegistration, self)._message_get_suggested_recipients() + public_users = self.env['res.users'].sudo() + public_groups = self.env.ref("base.group_public", raise_if_not_found=False) + if public_groups: + public_users = public_groups.sudo().with_context(active_test=False).mapped("users") + try: + for attendee in self: + is_public = attendee.sudo().with_context(active_test=False).partner_id.user_ids in public_users if public_users else False + if attendee.partner_id and not is_public: + attendee._message_add_suggested_recipient(recipients, partner=attendee.partner_id, reason=_('Customer')) + elif attendee.email: + attendee._message_add_suggested_recipient(recipients, email=attendee.email, reason=_('Customer Email')) + except AccessError: # no read access rights -> ignore suggested recipients + pass + return recipients + + def _message_get_default_recipients(self): + # Prioritize registration email over partner_id, which may be shared when a single + # partner booked multiple seats + return {r.id: { + 'partner_ids': [], + 'email_to': r.email, + 'email_cc': False} + for r in self} + + def _message_post_after_hook(self, message, msg_vals): + if self.email and not self.partner_id: + # we consider that posting a message with a specified recipient (not a follower, a specific one) + # on a document without customer means that it was created through the chatter using + # suggested recipients. This heuristic allows to avoid ugly hacks in JS. + new_partner = message.partner_ids.filtered(lambda partner: partner.email == self.email) + if new_partner: + self.search([ + ('partner_id', '=', False), + ('email', '=', new_partner.email), + ('state', 'not in', ['cancel']), + ]).write({'partner_id': new_partner.id}) + return super(EventRegistration, self)._message_post_after_hook(message, msg_vals) + + def action_send_badge_email(self): + """ Open a window to compose an email, with the template - 'event_badge' + message loaded by default + """ + self.ensure_one() + template = self.env.ref('event.event_registration_mail_template_badge') + compose_form = self.env.ref('mail.email_compose_message_wizard_form') + ctx = dict( + default_model='event.registration', + default_res_id=self.id, + default_use_template=bool(template), + default_template_id=template.id, + default_composition_mode='comment', + custom_layout="mail.mail_notification_light", + ) + return { + 'name': _('Compose Email'), + 'type': 'ir.actions.act_window', + 'view_mode': 'form', + 'res_model': 'mail.compose.message', + 'views': [(compose_form.id, 'form')], + 'view_id': compose_form.id, + 'target': 'new', + 'context': ctx, + } + + def get_date_range_str(self): + self.ensure_one() + today = fields.Datetime.now() + event_date = self.event_begin_date + diff = (event_date.date() - today.date()) + if diff.days <= 0: + return _('today') + elif diff.days == 1: + return _('tomorrow') + elif (diff.days < 7): + return _('in %d days') % (diff.days, ) + elif (diff.days < 14): + return _('next week') + elif event_date.month == (today + relativedelta(months=+1)).month: + return _('next month') + else: + return _('on ') + format_datetime(self.env, self.event_begin_date, tz=self.event_id.date_tz, dt_format='medium') + + def summary(self): + self.ensure_one() + return {'information': []} diff --git a/addons/event/models/event_stage.py b/addons/event/models/event_stage.py index d4064e3f9d4..e985df62c68 100644 --- a/addons/event/models/event_stage.py +++ b/addons/event/models/event_stage.py @@ -16,7 +16,6 @@ class EventStage(models.Model): pipe_end = fields.Boolean( string='End Stage', default=False, help='Events will be automatically be moved into this stage their are passed and event moved in the stage will be automatically greened') - legend_blocked = fields.Char( 'Red Kanban Label', default=lambda s: _('Blocked'), translate=True, required=True, help='Override the default value displayed for the blocked state for kanban selection.') diff --git a/addons/event/tests/test_event_internals.py b/addons/event/tests/test_event_internals.py index 905222dcc5f..2a79086808a 100644 --- a/addons/event/tests/test_event_internals.py +++ b/addons/event/tests/test_event_internals.py @@ -16,7 +16,7 @@ class TestEventData(TestEventCommon): @users('user_eventmanager') def test_event_date_computation(self): - self.patcher = patch('odoo.addons.event.models.event.fields.Datetime', wraps=FieldsDatetime) + 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, 31, 8, 0, 0) |
