From cc9933256a68fe51f2a058955aa401a3c3ebc7e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 23 Jun 2021 09:21:53 +0000 Subject: [PATCH] [IMP] website_event: create website menu for all events menus In website_event some event-specific frontend menus are not linked to ``website.event.menu`` like track or exhibitor sub-modules menus. This comes from initial implementation of ``website.event.menu`` that was available only for ``website_event_track``. Using ``website.event.menu`` eases menu management as it allows to have an object making a link between the event and the website menu. Notably when checking / unchecking in backend submenus it eases management. It also eases management when people manually edit menus from frontend as otherwise we have to manually manage ``ir.ui.view`` based on some naming manipulation. Task ID-2577079 See odoo/odoo#72411 --- addons/website_event/__manifest__.py | 2 +- addons/website_event/models/event_event.py | 121 ++++++++++++------ .../models/website_event_menu.py | 14 +- addons/website_event/models/website_menu.py | 7 +- addons/website_event/tests/common.py | 22 ++-- .../tests/test_event_internals.py | 48 +++++-- .../website_event/views/event_event_views.xml | 4 + .../views/website_event_menu_views.xml | 3 + .../website_event_track/models/event_type.py | 6 +- .../tests/test_website_event.py | 46 ++----- 10 files changed, 169 insertions(+), 104 deletions(-) diff --git a/addons/website_event/__manifest__.py b/addons/website_event/__manifest__.py index 90d1d7556b7..647f6e1ff51 100644 --- a/addons/website_event/__manifest__.py +++ b/addons/website_event/__manifest__.py @@ -3,7 +3,7 @@ { 'name': 'Events', - 'version': '1.3', + 'version': '1.4', 'category': 'Marketing/Events', 'sequence': 140, 'summary': 'Publish events, sell tickets', diff --git a/addons/website_event/models/event_event.py b/addons/website_event/models/event_event.py index f72e7857302..0292e8d14fa 100644 --- a/addons/website_event/models/event_event.py +++ b/addons/website_event/models/event_event.py @@ -36,12 +36,30 @@ class Event(models.Model): 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.") + help="Allows to display and manage event-specific menus on website.") menu_id = fields.Many2one('website.menu', 'Event Menu', copy=False) menu_register_cta = fields.Boolean( 'Extra Register Button', compute='_compute_menu_register_cta', readonly=False, store=True) + # sub-menus management + introduction_menu = fields.Boolean( + "Introduction Menu", compute="_compute_website_menu_data", + readonly=False, store=True) + introduction_menu_ids = fields.One2many( + "website.event.menu", "event_id", string="Introduction Menus", + domain=[("menu_type", "=", "introduction")]) + location_menu = fields.Boolean( + "Location Menu", compute="_compute_website_menu_data", + readonly=False, store=True) + location_menu_ids = fields.One2many( + "website.event.menu", "event_id", string="Location Menus", + domain=[("menu_type", "=", "location_menu")]) + register_menu = fields.Boolean( + "Register Menu", compute="_compute_website_menu_data", + readonly=False, store=True) + register_menu_ids = fields.One2many( + "website.event.menu", "event_id", string="Register Menus", + domain=[("menu_type", "=", "register")]) community_menu = fields.Boolean( "Community Menu", compute="_compute_community_menu", readonly=False, store=True, @@ -120,6 +138,15 @@ class Event(models.Model): for event in self: event.community_menu = False + @api.depends("website_menu") + def _compute_website_menu_data(self): + """ Synchronize with website_menu at change and let people update them + at will afterwards. """ + for event in self: + event.introduction_menu = event.website_menu + event.location_menu = event.website_menu + event.register_menu = event.website_menu + @api.depends("event_type_id", "website_menu") def _compute_menu_register_cta(self): """ At type onchange: synchronize. At website_menu update: synchronize. """ @@ -186,10 +213,15 @@ class Event(models.Model): :return list: list of fields, each of which triggering a menu update like website_menu, website_track, ... """ - return ['website_menu', 'community_menu'] + return ['community_menu', 'introduction_menu', 'location_menu', 'register_menu'] def _get_menu_type_field_matching(self): - return {'community': 'community_menu'} + return { + 'community': 'community_menu', + 'introduction': 'introduction_menu', + 'location': 'location_menu', + 'register': 'register_menu', + } def _split_menus_state_by_field(self): """ For each field linked to a menu, get the set of events having this @@ -248,14 +280,23 @@ class Event(models.Model): """ 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), + (_('Introduction'), False, 'website_event.template_intro', 1, 'introduction'), + (_('Location'), False, 'website_event.template_location', 50, 'location'), + (_('Register'), '/event/%s/register' % slug(self), False, 100, 'register'), + (_('Community'), '/event/%s/community' % slug(self), False, 80, 'community'), ] + def _get_introduction_menu_entries(self): + return [self._get_website_menu_entries()[0]] + + def _get_location_menu_entries(self): + return [self._get_website_menu_entries()[1]] + + def _get_register_menu_entries(self): + return [self._get_website_menu_entries()[2]] + def _get_community_menu_entries(self): - self.ensure_one() - return [(_('Community'), '/event/%s/community' % slug(self), False, 80, 'community')] + return [self._get_website_menu_entries()[3]] def _update_website_menus(self, menus_update_by_field=None): """ Synchronize event configuration and its menu entries for frontend. @@ -267,11 +308,14 @@ class Event(models.Model): 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') + if event.menu_id and (not menus_update_by_field or event in menus_update_by_field.get('introduction_menu')): + event._update_website_menu_entry('introduction_menu', 'introduction_menu_ids', '_get_introduction_menu_entries') + if event.menu_id and (not menus_update_by_field or event in menus_update_by_field.get('location_menu')): + event._update_website_menu_entry('location_menu', 'location_menu_ids', '_get_location_menu_entries') + if event.menu_id and (not menus_update_by_field or event in menus_update_by_field.get('register_menu')): + event._update_website_menu_entry('register_menu', 'register_menu_ids', '_get_register_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 @@ -290,15 +334,8 @@ class Event(models.Model): 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 + for menu_data in getattr(self, method_name)(): + (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 @@ -306,20 +343,32 @@ class Event(models.Model): 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. + def _create_menu(self, sequence, name, url, xml_id, menu_type): + """ Create a new menu for the current event. + + 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 using the qweb template given by its + xml_id. 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. This is currently + limited to two menus: introduction and location. :param menu_type: type of menu. Mainly used for inheritance purpose - allowing more fine-grain tuning of menus. """ + allowing more fine-grain tuning of menus. + """ self.check_access_rights('write') + view_id = False if not url: - self.env['ir.ui.view'].with_context(_force_unlink=True).sudo().search([('name', '=', name + ' ' + self.name)]).unlink() - page_result = self.env['website'].sudo().new_page(name + ' ' + self.name, template=xml_id, ispage=False) + # add_menu=False, ispage=False -> simply create a new ir.ui.view with name + # and template + page_result = self.env['website'].sudo().new_page( + name=name + ' ' + self.name, template=xml_id, + add_menu=False, ispage=False) url = "/event/" + slug(self) + "/page" + page_result['url'] # url contains starting "/" + view_id = page_result['view_id'] + website_menu = self.env['website.menu'].sudo().create({ 'name': name, 'url': url, @@ -327,12 +376,12 @@ class Event(models.Model): '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, - }) + self.env['website.event.menu'].create({ + 'menu_id': website_menu.id, + 'event_id': self.id, + 'menu_type': menu_type, + 'view_id': view_id, + }) return website_menu # ------------------------------------------------------------ diff --git a/addons/website_event/models/website_event_menu.py b/addons/website_event/models/website_event_menu.py index b19c7d8ca75..63a2c63f8a6 100644 --- a/addons/website_event/models/website_event_menu.py +++ b/addons/website_event/models/website_event_menu.py @@ -10,6 +10,14 @@ class EventMenu(models.Model): 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) + view_id = fields.Many2one('ir.ui.view', string='View', ondelete='cascade', help='Used when not being an url based menu') + menu_type = fields.Selection( + [('community', 'Community Menu'), + ('introduction', 'Introduction'), + ('location', 'Location'), + ('register', 'Register'), + ], string="Menu Type", required=True) + + def unlink(self): + self.view_id.sudo().unlink() + return super(EventMenu, self).unlink() diff --git a/addons/website_event/models/website_menu.py b/addons/website_event/models/website_menu.py index 1b96cd8552c..00f3a90814d 100644 --- a/addons/website_event/models/website_menu.py +++ b/addons/website_event/models/website_menu.py @@ -8,8 +8,7 @@ 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. """ + """ Override to synchronize event configuration fields with menu deletion. """ event_updates = {} website_event_menus = self.env['website.event.menu'].search([('menu_id', 'in', self.ids)]) for event_menu in website_event_menus: @@ -18,7 +17,9 @@ class WebsiteMenu(models.Model): if event_menu.menu_type == menu_type: to_update.append(fname) - # call super that resumes the unlink of menus entries (including website event menus) + # manually remove website_event_menus to call their ``unlink`` method. Otherwise + # super unlinks at db level and skip model-specific behavior. + website_event_menus.unlink() res = super(WebsiteMenu, self).unlink() # update events diff --git a/addons/website_event/tests/common.py b/addons/website_event/tests/common.py index 3773179fe83..7fe660d403f 100644 --- a/addons/website_event/tests/common.py +++ b/addons/website_event/tests/common.py @@ -73,28 +73,28 @@ class TestWebsiteEventCommon(TestEventCommon): ) def _get_menus(self): - return set(['Introduction', 'Location', 'Register']) + return set(['Introduction', 'Location', 'Register', 'Community']) - def _assert_website_menus(self, event, menu_entries=None): + def _assert_website_menus(self, event, menus_in=None, menus_out=None): self.assertTrue(event.menu_id) - if menu_entries is None: - menu_entries = self._get_menus() + if menus_in is None: + menus_in = list(self._get_menus()) menus = self.env['website.menu'].search([('parent_id', '=', event.menu_id.id)]) - self.assertEqual(len(menus), len(menu_entries)) - self.assertEqual(set(menus.mapped('name')), menu_entries) + self.assertTrue(len(menus) >= len(menus_in)) + self.assertTrue(all(menu_name in menus.mapped('name') for menu_name in menus_in)) + if menus_out: + self.assertTrue(all(menu_name not in menus.mapped('name') for menu_name in menus_out)) for page_specific in ['Introduction', 'Location']: view = self.env['ir.ui.view'].search( [('name', '=', page_specific + ' ' + event.name)] ) - if page_specific in menu_entries: + if page_specific in menus_in: self.assertTrue(bool(view)) - # TDE FIXME: page deletion not done in 13.3 for Introduction/Location, difficult to fix - # without website.event.menu model (or crappy code based on name) - # else: - # self.assertFalse(bool(view)) + else: + self.assertFalse(bool(view)) class TestEventOnlineCommon(TestEventCommon, EventDtPatcher): diff --git a/addons/website_event/tests/test_event_internals.py b/addons/website_event/tests/test_event_internals.py index 9339da20e6a..60643a32b7d 100644 --- a/addons/website_event/tests/test_event_internals.py +++ b/addons/website_event/tests/test_event_internals.py @@ -11,7 +11,7 @@ from odoo.tests.common import users class TestEventWebsite(TestWebsiteEventCommon): @users('user_eventmanager') - def test_menu_create(self): + def test_menu_management(self): event = self.env['event.event'].create({ 'name': 'TestEvent', 'date_begin': fields.Datetime.to_string(datetime.today() + timedelta(days=1)), @@ -19,7 +19,33 @@ class TestEventWebsite(TestWebsiteEventCommon): 'website_menu': True, 'community_menu': False, }) - self._assert_website_menus(event) + self.assertTrue(event.website_menu) + self.assertTrue(event.introduction_menu) + self.assertTrue(event.location_menu) + self.assertTrue(event.register_menu) + self.assertFalse(event.community_menu) + self._assert_website_menus(event, ['Introduction', 'Location', 'Register'], menus_out=['Community']) + + event.community_menu = True + self._assert_website_menus(event, ['Introduction', 'Location', 'Register', 'Community']) + + # test create without any requested menus + event = self.env['event.event'].create({ + 'name': 'TestEvent', + 'date_begin': fields.Datetime.to_string(datetime.today() + timedelta(days=1)), + 'date_end': fields.Datetime.to_string(datetime.today() + timedelta(days=15)), + 'website_menu': False, + }) + self.assertFalse(event.website_menu) + self.assertFalse(event.introduction_menu) + self.assertFalse(event.location_menu) + self.assertFalse(event.register_menu) + self.assertFalse(event.community_menu) + self.assertFalse(event.menu_id) + + # test update of website_menu triggering 3 sub menus + event.write({'website_menu': True}) + self._assert_website_menus(event, ['Introduction', 'Location', 'Register'], menus_out=['Community']) @users('user_event_web_manager') def test_menu_management_frontend(self): @@ -30,18 +56,14 @@ class TestEventWebsite(TestWebsiteEventCommon): 'website_menu': True, 'community_menu': False, }) - self.assertTrue(event.website_menu) - self._assert_website_menus(event) + self._assert_website_menus(event, ['Introduction', 'Location', 'Register'], menus_out=['Community']) - introduction_menu = event.menu_id.child_id.filtered(lambda menu: menu.name == 'Introduction') - introduction_menu.unlink() + # simulate menu removal from frontend: aka unlinking a menu + event.menu_id.child_id.filtered(lambda menu: menu.name == 'Introduction').unlink() self.assertTrue(event.website_menu) - self._assert_website_menus(event, set(['Location', 'Register'])) + self._assert_website_menus(event, ['Location', 'Register'], menus_out=['Introduction', 'Community']) - @users('user_eventmanager') - def test_menu_update(self): - event = self.env['event.event'].browse(self.event_0.id) - self.assertFalse(event.menu_id) - event.website_menu = True - self._assert_website_menus(event) + # re-created from backend + event.introduction_menu = True + self._assert_website_menus(event, ['Introduction', 'Location', 'Register'], menus_out=['Community']) diff --git a/addons/website_event/views/event_event_views.xml b/addons/website_event/views/event_event_views.xml index 369e0658516..4de480e95bf 100644 --- a/addons/website_event/views/event_event_views.xml +++ b/addons/website_event/views/event_event_views.xml @@ -23,6 +23,10 @@