diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/website_event/models | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/website_event/models')
| -rw-r--r-- | addons/website_event/models/__init__.py | 10 | ||||
| -rw-r--r-- | addons/website_event/models/event_event.py | 396 | ||||
| -rw-r--r-- | addons/website_event/models/event_registration.py | 14 | ||||
| -rw-r--r-- | addons/website_event/models/event_type.py | 28 | ||||
| -rw-r--r-- | addons/website_event/models/website.py | 14 | ||||
| -rw-r--r-- | addons/website_event/models/website_event_menu.py | 15 | ||||
| -rw-r--r-- | addons/website_event/models/website_menu.py | 29 | ||||
| -rw-r--r-- | addons/website_event/models/website_visitor.py | 126 |
8 files changed, 632 insertions, 0 deletions
diff --git a/addons/website_event/models/__init__.py b/addons/website_event/models/__init__.py new file mode 100644 index 00000000..cce22a56 --- /dev/null +++ b/addons/website_event/models/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import event_event +from . import event_registration +from . import event_type +from . import website +from . import website_event_menu +from . import website_menu +from . import website_visitor diff --git a/addons/website_event/models/event_event.py b/addons/website_event/models/event_event.py new file mode 100644 index 00000000..6fe54ab8 --- /dev/null +++ b/addons/website_event/models/event_event.py @@ -0,0 +1,396 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import json +import werkzeug.urls + +from pytz import utc + +from odoo import api, fields, models, _ +from odoo.addons.http_routing.models.ir_http import slug +from odoo.osv import expression + +GOOGLE_CALENDAR_URL = 'https://www.google.com/calendar/render?' + + +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 + is_participating = fields.Boolean("Is Participating", compute="_compute_is_participating") + # website + website_published = fields.Boolean(tracking=True) + website_menu = fields.Boolean( + string='Website Menu', + compute='_compute_website_menu', readonly=False, store=True, + help="Creates menus Introduction, Location and Register on the page " + "of the event on the website.") + menu_id = fields.Many2one('website.menu', 'Event Menu', copy=False) + menu_register_cta = fields.Boolean( + 'Add Register Button', compute='_compute_menu_register_cta', + readonly=False, store=True) + community_menu = fields.Boolean( + "Community Menu", compute="_compute_community_menu", + readonly=False, store=True, + help="Display community tab on website") + community_menu_ids = fields.One2many( + "website.event.menu", "event_id", string="Event Community Menus", + domain=[("menu_type", "=", "community")]) + # live information + is_ongoing = fields.Boolean( + 'Is Ongoing', compute='_compute_time_data', search='_search_is_ongoing', + help="Whether event has begun") + is_done = fields.Boolean( + 'Is Done', compute='_compute_time_data', + help="Whether event is finished") + start_today = fields.Boolean( + 'Start Today', compute='_compute_time_data', + help="Whether event is going to start today if still not ongoing") + start_remaining = fields.Integer( + 'Remaining before start', compute='_compute_time_data', + help="Remaining time before event starts (minutes)") + + def _compute_is_participating(self): + """Heuristic + + * public, no visitor: not participating as we have no information; + * public and visitor: check visitor is linked to a registration. As + visitors are merged on the top parent, current visitor check is + sufficient even for successive visits; + * logged, no visitor: check partner is linked to a registration. Do + not check the email as it is not really secure; + * logged as visitor: check partner or visitor are linked to a + registration; + """ + current_visitor = self.env['website.visitor']._get_visitor_from_request(force_create=False) + if self.env.user._is_public() and not current_visitor: + events = self.env['event.event'] + elif self.env.user._is_public(): + events = self.env['event.registration'].sudo().search([ + ('event_id', 'in', self.ids), + ('state', '!=', 'cancel'), + ('visitor_id', '=', current_visitor.id), + ]).event_id + else: + if current_visitor: + domain = [ + '|', + ('partner_id', '=', self.env.user.partner_id.id), + ('visitor_id', '=', current_visitor.id) + ] + else: + domain = [('partner_id', '=', self.env.user.partner_id.id)] + events = self.env['event.registration'].sudo().search( + expression.AND([ + domain, + ['&', ('event_id', 'in', self.ids), ('state', '!=', 'cancel')] + ]) + ).event_id + + for event in self: + event.is_participating = event in events + + @api.depends('event_type_id') + def _compute_website_menu(self): + """ Also ensure a value for website_menu as it is a trigger notably for + track related menus. """ + for event in self: + if event.event_type_id and event.event_type_id != event._origin.event_type_id: + event.website_menu = event.event_type_id.website_menu + elif not event.website_menu: + event.website_menu = False + + @api.depends("event_type_id", "website_menu", "community_menu") + def _compute_community_menu(self): + """ Set False in base module. Sub modules will add their own logic + (meet or track_quiz). """ + for event in self: + event.community_menu = False + + @api.depends("event_type_id", "website_menu") + def _compute_menu_register_cta(self): + """ At type onchange: synchronize. At website_menu update: synchronize. """ + for event in self: + if event.event_type_id and event.event_type_id != event._origin.event_type_id: + event.menu_register_cta = event.event_type_id.menu_register_cta + elif event.website_menu and (event.website_menu != event._origin.website_menu or not event.menu_register_cta): + event.menu_register_cta = True + elif not event.website_menu: + event.menu_register_cta = False + + @api.depends('date_begin', 'date_end') + def _compute_time_data(self): + """ Compute start and remaining time. Do everything in UTC as we compute only + time deltas here. """ + now_utc = utc.localize(fields.Datetime.now().replace(microsecond=0)) + for event in self: + date_begin_utc = utc.localize(event.date_begin, is_dst=False) + date_end_utc = utc.localize(event.date_end, is_dst=False) + event.is_ongoing = date_begin_utc <= now_utc <= date_end_utc + event.is_done = now_utc > date_end_utc + event.start_today = date_begin_utc.date() == now_utc.date() + if date_begin_utc >= now_utc: + td = date_begin_utc - now_utc + event.start_remaining = int(td.total_seconds() / 60) + else: + event.start_remaining = 0 + + @api.depends('name') + def _compute_website_url(self): + super(Event, self)._compute_website_url() + for event in self: + 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) + + # ------------------------------------------------------------ + # CRUD + # ------------------------------------------------------------ + + @api.model + def create(self, vals): + res = super(Event, self).create(vals) + res._update_website_menus() + return res + + def write(self, vals): + menus_state_by_field = self._split_menus_state_by_field() + res = super(Event, self).write(vals) + menus_update_by_field = self._get_menus_update_by_field(menus_state_by_field, force_update=vals.keys()) + self._update_website_menus(menus_update_by_field=menus_update_by_field) + return res + + # ------------------------------------------------------------ + # WEBSITE MENU MANAGEMENT + # ------------------------------------------------------------ + + def toggle_website_menu(self, val): + self.website_menu = val + + def _get_menu_update_fields(self): + """" Return a list of fields triggering a split of menu to activate / + menu to de-activate. Due to saas-13.3 improvement of menu management + this is done using side-methods to ease inheritance. + + :return list: list of fields, each of which triggering a menu update + like website_menu, website_track, ... """ + return ['website_menu', 'community_menu'] + + def _get_menu_type_field_matching(self): + return {'community': 'community_menu'} + + def _split_menus_state_by_field(self): + """ For each field linked to a menu, get the set of events having this + menu activated and de-activated. Purpose is to find those whose value + changed and update the underlying menus. + + :return dict: key = name of field triggering a website menu update, get { + 'activated': subset of self having its menu currently set to True + 'deactivated': subset of self having its menu currently set to False + } """ + menus_state_by_field = dict() + for fname in self._get_menu_update_fields(): + activated = self.filtered(lambda event: event[fname]) + menus_state_by_field[fname] = { + 'activated': activated, + 'deactivated': self - activated, + } + return menus_state_by_field + + def _get_menus_update_by_field(self, menus_state_by_field, force_update=None): + """ For each field linked to a menu, get the set of events requiring + this menu to be activated or de-activated based on previous recorded + value. + + :param menus_state_by_field: see ``_split_menus_state_by_field``; + :param force_update: list of field to which we force update of menus. This + is used notably when a direct write to a stored editable field messes with + its pre-computed value, notably in a transient mode (aka demo for example); + + :return dict: key = name of field triggering a website menu update, get { + 'activated': subset of self having its menu toggled to True + 'deactivated': subset of self having its menu toggled to False + } """ + menus_update_by_field = dict() + for fname in self._get_menu_update_fields(): + if fname in force_update: + menus_update_by_field[fname] = self + else: + menus_update_by_field[fname] = self.env['event.event'] + menus_update_by_field[fname] |= menus_state_by_field[fname]['activated'].filtered(lambda event: not event[fname]) + menus_update_by_field[fname] |= menus_state_by_field[fname]['deactivated'].filtered(lambda event: event[fname]) + return menus_update_by_field + + def _get_website_menu_entries(self): + """ Method returning menu entries to display on the website view of the + event, possibly depending on some options in inheriting modules. + + Each menu entry is a tuple containing : + * name: menu item name + * url: if set, url to a route (do not use xml_id in that case); + * xml_id: template linked to the page (do not use url in that case); + * sequence: specific sequence of menu entry to be set on the menu; + * menu_type: type of menu entry (used in inheriting modules to ease + menu management; not used in this module in 13.3 due to technical + limitations); + """ + self.ensure_one() + return [ + (_('Introduction'), False, 'website_event.template_intro', 1, False), + (_('Location'), False, 'website_event.template_location', 50, False), + (_('Register'), '/event/%s/register' % slug(self), False, 100, False), + ] + + def _get_community_menu_entries(self): + self.ensure_one() + return [(_('Community'), '/event/%s/community' % slug(self), False, 80, 'community')] + + def _update_website_menus(self, menus_update_by_field=None): + """ Synchronize event configuration and its menu entries for frontend. + + :param menus_update_by_field: see ``_get_menus_update_by_field``""" + for event in self: + if event.menu_id and not event.website_menu: + event.menu_id.sudo().unlink() + elif event.website_menu and not event.menu_id: + root_menu = self.env['website.menu'].sudo().create({'name': event.name, 'website_id': event.website_id.id}) + event.menu_id = root_menu + if event.website_menu and (not menus_update_by_field or event in menus_update_by_field.get('website_menu')): + for name, url, xml_id, menu_sequence, menu_type in event._get_website_menu_entries(): + event._create_menu(menu_sequence, name, url, xml_id, menu_type=menu_type) + if event.menu_id and (not menus_update_by_field or event in menus_update_by_field.get('community_menu')): + event._update_website_menu_entry('community_menu', 'community_menu_ids', '_get_community_menu_entries') + + def _update_website_menu_entry(self, fname_bool, fname_o2m, method_name): + """ Generic method to create menu entries based on a flag on event. This + method is a bit obscure, but is due to preparation of adding new menus + entries and pages for event in a stable version, leading to some constraints + while developing. + + :param fname_bool: field name (e.g. website_track) + :param fname_o2m: o2m linking towards website.event.menu matching the + boolean fields (normally an entry ot website.event.menu with type matching + the boolean field name) + :param method_name: method returning menu entries information: url, sequence, ... + """ + self.ensure_one() + new_menu = None + + if self[fname_bool] and not self[fname_o2m]: + # menus not found but boolean True: get menus to create + for sequence, menu_data in enumerate(getattr(self, method_name)()): + # some modules have 4 data: name, url, xml_id, menu_type; however we + # plan to support sequence in future modules, so this hackish code is + # necessary to avoid crashing. Not nice, but stable target = meh. + if len(menu_data) == 4: + (name, url, xml_id, menu_type) = menu_data + menu_sequence = sequence + elif len(menu_data) == 5: + (name, url, xml_id, menu_sequence, menu_type) = menu_data + new_menu = self._create_menu(menu_sequence, name, url, xml_id, menu_type=menu_type) + elif not self[fname_bool]: + # will cascade delete to the website.event.menu + self[fname_o2m].mapped('menu_id').sudo().unlink() + + return new_menu + + def _create_menu(self, sequence, name, url, xml_id, menu_type=False): + """ If url: create a website menu. Menu leads directly to the URL that + should be a valid route. If xml_id: create a new page, take its url back + thanks to new_page of website, then link it to a menu. Template is + duplicated and linked to a new url, meaning each menu will have its own + copy of the template. + + :param menu_type: type of menu. Mainly used for inheritance purpose + allowing more fine-grain tuning of menus. """ + if not url: + self.env['ir.ui.view'].with_context(_force_unlink=True).search([('name', '=', name + ' ' + self.name)]).unlink() + page_result = self.env['website'].sudo().new_page(name + ' ' + self.name, template=xml_id, ispage=False) + url = "/event/" + slug(self) + "/page" + page_result['url'] # url contains starting "/" + website_menu = self.env['website.menu'].sudo().create({ + 'name': name, + 'url': url, + 'parent_id': self.menu_id.id, + 'sequence': sequence, + 'website_id': self.website_id.id, + }) + if menu_type: + self.env['website.event.menu'].create({ + 'menu_id': website_menu.id, + 'event_id': self.id, + 'menu_type': menu_type, + }) + return website_menu + + # ------------------------------------------------------------ + # TOOLS + # ------------------------------------------------------------ + + def google_map_link(self, zoom=8): + """ Temporary method for stable """ + return self._google_map_link(zoom=zoom) + + def _google_map_link(self, zoom=8): + self.ensure_one() + if self.address_id: + return self.sudo().address_id.google_map_link(zoom=zoom) + return None + + def _track_subtype(self, init_values): + self.ensure_one() + if 'is_published' in init_values and self.is_published: + return self.env.ref('website_event.mt_event_published') + elif 'is_published' in init_values and not self.is_published: + return self.env.ref('website_event.mt_event_unpublished') + return super(Event, self)._track_subtype(init_values) + + def action_open_badge_editor(self): + """ open the event badge editor : redirect to the report page of event badge report """ + self.ensure_one() + return { + 'type': 'ir.actions.act_url', + 'target': 'new', + 'url': '/report/html/%s/%s?enable_editor' % ('event.event_event_report_template_badge', self.id), + } + + def _get_event_resource_urls(self): + url_date_start = self.date_begin.strftime('%Y%m%dT%H%M%SZ') + url_date_stop = self.date_end.strftime('%Y%m%dT%H%M%SZ') + params = { + 'action': 'TEMPLATE', + 'text': self.name, + 'dates': url_date_start + '/' + url_date_stop, + 'details': self.name, + } + if self.address_id: + params.update(location=self.sudo().address_id.contact_address.replace('\n', ' ')) + encoded_params = werkzeug.urls.url_encode(params) + google_url = GOOGLE_CALENDAR_URL + encoded_params + iCal_url = '/event/%d/ics?%s' % (self.id, encoded_params) + return {'google_url': google_url, 'iCal_url': iCal_url} + + def _default_website_meta(self): + res = super(Event, self)._default_website_meta() + event_cover_properties = json.loads(self.cover_properties) + # background-image might contain single quotes eg `url('/my/url')` + res['default_opengraph']['og:image'] = res['default_twitter']['twitter:image'] = event_cover_properties.get('background-image', 'none')[4:-1].strip("'") + res['default_opengraph']['og:title'] = res['default_twitter']['twitter:title'] = self.name + res['default_opengraph']['og:description'] = res['default_twitter']['twitter:description'] = self.subtitle + res['default_twitter']['twitter:card'] = 'summary' + res['default_meta_description'] = self.subtitle + return res + + def get_backend_menu_id(self): + return self.env.ref('event.event_main_menu').id diff --git a/addons/website_event/models/event_registration.py b/addons/website_event/models/event_registration.py new file mode 100644 index 00000000..f62f2069 --- /dev/null +++ b/addons/website_event/models/event_registration.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class EventRegistration(models.Model): + _name = 'event.registration' + _inherit = ['event.registration'] + + visitor_id = fields.Many2one('website.visitor', string='Visitor', ondelete='set null') + + def _get_website_registration_allowed_fields(self): + return {'name', 'phone', 'email', 'mobile', 'event_id', 'partner_id', 'event_ticket_id'} diff --git a/addons/website_event/models/event_type.py b/addons/website_event/models/event_type.py new file mode 100644 index 00000000..43acf033 --- /dev/null +++ b/addons/website_event/models/event_type.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models + + +class EventType(models.Model): + _name = 'event.type' + _inherit = ['event.type'] + + website_menu = fields.Boolean('Display a dedicated menu on Website') + community_menu = fields.Boolean( + "Community Menu", compute="_compute_community_menu", + readonly=False, store=True, + help="Display community tab on website") + menu_register_cta = fields.Boolean( + 'Add Register Button', compute='_compute_menu_register_cta', + readonly=False, store=True) + + @api.depends('website_menu') + def _compute_community_menu(self): + for event_type in self: + event_type.community_menu = event_type.website_menu + + @api.depends('website_menu') + def _compute_menu_register_cta(self): + for event_type in self: + event_type.menu_register_cta = event_type.website_menu diff --git a/addons/website_event/models/website.py b/addons/website_event/models/website.py new file mode 100644 index 00000000..bb34800a --- /dev/null +++ b/addons/website_event/models/website.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models, _ +from odoo.addons.http_routing.models.ir_http import url_for + + +class Website(models.Model): + _inherit = "website" + + def get_suggested_controllers(self): + suggested_controllers = super(Website, self).get_suggested_controllers() + suggested_controllers.append((_('Events'), url_for('/event'), 'website_event')) + return suggested_controllers diff --git a/addons/website_event/models/website_event_menu.py b/addons/website_event/models/website_event_menu.py new file mode 100644 index 00000000..b19c7d8c --- /dev/null +++ b/addons/website_event/models/website_event_menu.py @@ -0,0 +1,15 @@ +# -*- 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([ + ('community', 'Community Menu')], string="Menu Type", + ondelete={'community': 'cascade'}, required=True) diff --git a/addons/website_event/models/website_menu.py b/addons/website_event/models/website_menu.py new file mode 100644 index 00000000..1b96cd85 --- /dev/null +++ b/addons/website_event/models/website_menu.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models + + +class WebsiteMenu(models.Model): + _inherit = "website.menu" + + def unlink(self): + """ Override to synchronize event configuration fields with menu deletion. + This should be cleaned in upcoming versions. """ + event_updates = {} + website_event_menus = self.env['website.event.menu'].search([('menu_id', 'in', self.ids)]) + for event_menu in website_event_menus: + to_update = event_updates.setdefault(event_menu.event_id, list()) + for menu_type, fname in event_menu.event_id._get_menu_type_field_matching().items(): + if event_menu.menu_type == menu_type: + to_update.append(fname) + + # call super that resumes the unlink of menus entries (including website event menus) + res = super(WebsiteMenu, self).unlink() + + # update events + for event, to_update in event_updates.items(): + if to_update: + event.write(dict((fname, False) for fname in to_update)) + + return res diff --git a/addons/website_event/models/website_visitor.py b/addons/website_event/models/website_visitor.py new file mode 100644 index 00000000..078e4f03 --- /dev/null +++ b/addons/website_event/models/website_visitor.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models + + +class WebsiteVisitor(models.Model): + _name = 'website.visitor' + _inherit = ['website.visitor'] + + parent_id = fields.Many2one( + 'website.visitor', string="Parent", ondelete='set null', + help="Main identity") + event_registration_ids = fields.One2many( + 'event.registration', 'visitor_id', string='Event Registrations', + groups="event.group_event_user") + event_registration_count = fields.Integer( + '# Registrations', compute='_compute_event_registration_count', + groups="event.group_event_user") + event_registered_ids = fields.Many2many( + 'event.event', string="Registered Events", + compute="_compute_event_registered_ids", compute_sudo=True, + search="_search_event_registered_ids", + groups="event.group_event_user") + + @api.depends('event_registration_ids') + def _compute_event_registration_count(self): + if self.ids: + read_group_res = self.env['event.registration'].read_group( + [('visitor_id', 'in', self.ids)], + ['visitor_id'], ['visitor_id']) + visitor_mapping = dict( + (item['visitor_id'][0], item['visitor_id_count']) + for item in read_group_res) + else: + visitor_mapping = dict() + for visitor in self: + visitor.event_registration_count = visitor_mapping.get(visitor.id) or 0 + + @api.depends('event_registration_ids.email', 'event_registration_ids.mobile', 'event_registration_ids.phone') + def _compute_email_phone(self): + super(WebsiteVisitor, self)._compute_email_phone() + self.flush() + + for visitor in self.filtered(lambda visitor: not visitor.email or not visitor.mobile): + linked_registrations = visitor.event_registration_ids.sorted(lambda reg: (reg.create_date, reg.id), reverse=False) + if not visitor.email: + visitor.email = next((reg.email for reg in linked_registrations if reg.email), False) + if not visitor.mobile: + visitor.mobile = next((reg.mobile or reg.phone for reg in linked_registrations if reg.mobile or reg.phone), False) + + @api.depends('parent_id', 'event_registration_ids') + def _compute_event_registered_ids(self): + # include parent's registrations in a visitor o2m field. We don't add + # child one as child should not have registrations (moved to the parent) + for visitor in self: + all_registrations = visitor.event_registration_ids | visitor.parent_id.event_registration_ids + visitor.event_registered_ids = all_registrations.mapped('event_id') + + def _search_event_registered_ids(self, operator, operand): + """ Search visitors with terms on events within their event registrations. E.g. [('event_registered_ids', + 'in', [1, 2])] should return visitors having a registration on events 1, 2 as + well as their children for notification purpose. """ + if operator == "not in": + raise NotImplementedError("Unsupported 'Not In' operation on visitors registrations") + + all_registrations = self.env['event.registration'].sudo().search([ + ('event_id', operator, operand) + ]) + if all_registrations: + # search children, even archived one, to contact them + visitors = all_registrations.with_context(active_test=False).mapped('visitor_id') + children = self.env['website.visitor'].with_context( + active_test=False + ).sudo().search([('parent_id', 'in', visitors.ids)]) + visitor_ids = (visitors + children).ids + else: + visitor_ids = [] + + return [('id', 'in', visitor_ids)] + + def _link_to_partner(self, partner, update_values=None): + """ Propagate partner update to registration records """ + if partner: + registration_wo_partner = self.event_registration_ids.filtered(lambda registration: not registration.partner_id) + if registration_wo_partner: + registration_wo_partner.partner_id = partner + super(WebsiteVisitor, self)._link_to_partner(partner, update_values=update_values) + + def _link_to_visitor(self, target, keep_unique=True): + """ Override linking process to link registrations to the final visitor. """ + self.event_registration_ids.write({'visitor_id': target.id}) + + res = super(WebsiteVisitor, self)._link_to_visitor(target, keep_unique=False) + + if keep_unique: + self.partner_id = False + self.parent_id = target.id + self.active = False + + return res + + def _get_visitor_from_request(self, force_create=False): + """ When fetching visitor, now that duplicates are linked to a main visitor + instead of unlinked, you may have more collisions issues with cookie being + set after a de-connection for example. + + In base method, visitor associated to a partner in case of public user is + not taken into account. It is considered as desynchronized cookie. Here + we also discard if the visitor has a main visitor whose partner is set + (aka wrong after logout partner). """ + visitor = super(WebsiteVisitor, self)._get_visitor_from_request(force_create=force_create) + + # also check that visitor parent partner is not different from user's one (indicates duplicate due to invalid or wrong cookie) + if visitor and visitor.parent_id.partner_id: + if self.env.user._is_public(): + visitor = self.env['website.visitor'].sudo() + elif not visitor.partner_id: + visitor = self.env['website.visitor'].sudo().with_context(active_test=False).search( + [('partner_id', '=', self.env.user.partner_id.id)] + ) + + if not visitor and force_create: + visitor = self._create_visitor() + + return visitor |
