[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
This commit is contained in:
Thibault Delavallée
2021-07-07 08:27:54 +00:00
parent 0d755e7127
commit cc9933256a
10 changed files with 169 additions and 104 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
{
'name': 'Events',
'version': '1.3',
'version': '1.4',
'category': 'Marketing/Events',
'sequence': 140,
'summary': 'Publish events, sell tickets',
+85 -36
View File
@@ -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
# ------------------------------------------------------------
@@ -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()
+4 -3
View File
@@ -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
+11 -11
View File
@@ -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):
@@ -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'])
@@ -23,6 +23,10 @@
<div name="event_menu_configuration" groups="base.group_no_one">
<label for="website_menu" string="Website Submenu"/>
<field name="website_menu"/>
<!-- hidden sub-menus, they are triggered all at once based on "website_menu" -->
<field name="introduction_menu" invisible="1"/>
<field name="location_menu" invisible="1"/>
<field name="register_menu" invisible="1"/>
<label for="menu_register_cta" string="Extra Register Button"/>
<field name="menu_register_cta"/>
<label for="community_menu" string="Community" invisible="1"/>
@@ -9,6 +9,7 @@
<field name="menu_id"/>
<field name="event_id"/>
<field name="menu_type"/>
<field name="view_id"/>
</search>
</field>
</record>
@@ -23,6 +24,7 @@
<field name="menu_id"/>
<field name="event_id"/>
<field name="menu_type"/>
<field name="view_id"/>
</group>
</sheet>
</form>
@@ -37,6 +39,7 @@
<field name="menu_id"/>
<field name="event_id"/>
<field name="menu_type"/>
<field name="view_id"/>
</tree>
</field>
</record>
@@ -8,14 +8,14 @@ class EventType(models.Model):
_inherit = 'event.type'
website_track = fields.Boolean(
string='Tracks on Website', compute='_compute_website_menu_data',
string='Tracks on Website', compute='_compute_website_track_menu_data',
readonly=False, store=True)
website_track_proposal = fields.Boolean(
string='Tracks Proposals on Website', compute='_compute_website_menu_data',
string='Tracks Proposals on Website', compute='_compute_website_track_menu_data',
readonly=False, store=True)
@api.depends('website_menu')
def _compute_website_menu_data(self):
def _compute_website_track_menu_data(self):
""" Simply activate or de-activate all menus at once. """
for event_type in self:
event_type.website_track = event_type.website_menu
@@ -11,7 +11,7 @@ from odoo.tests.common import users
class TestEventWebsiteTrack(TestWebsiteEventCommon):
def _get_menus(self):
return super(TestEventWebsiteTrack, self)._get_menus() | set(['Community', 'Talks', 'Agenda', 'Talk Proposals'])
return super(TestEventWebsiteTrack, self)._get_menus() | set(['Talks', 'Agenda', 'Talk Proposals'])
@users('user_eventmanager')
def test_create_menu(self):
@@ -28,13 +28,15 @@ class TestEventWebsiteTrack(TestWebsiteEventCommon):
'website_track': True,
'website_track_proposal': True,
}
if 'exhibitor_menu' in self.env['event.event']:
vals['exhibitor_menu'] = False
event = self.env['event.event'].create(vals)
self._assert_website_menus(event)
event.write({
'website_track': False,
'website_track_proposal': False,
})
self._assert_website_menus(event, ['Introduction', 'Location', 'Register', 'Community'], menus_out=['Talks', 'Agenda', 'Talk Proposals'])
@users('user_event_web_manager')
def test_menu_management_frontend(self):
vals = {
@@ -46,17 +48,14 @@ class TestEventWebsiteTrack(TestWebsiteEventCommon):
'website_track': True,
'website_track_proposal': True,
}
if 'exhibitor_menu' in self.env['event.event']:
vals['exhibitor_menu'] = False
event = self.env['event.event'].create(vals)
self.assertTrue(event.website_track)
self.assertTrue(event.website_track_proposal)
self._assert_website_menus(event)
self._assert_website_menus(event, self._get_menus())
introduction_menu = event.menu_id.child_id.filtered(lambda menu: menu.name == 'Introduction')
introduction_menu.unlink()
self._assert_website_menus(event, set(['Location', 'Register', 'Community', 'Talks', 'Agenda', 'Talk Proposals']))
self._assert_website_menus(event, ['Location', 'Register', 'Community', 'Talks', 'Agenda', 'Talk Proposals'], menus_out=["Introduction"])
menus = event.menu_id.child_id.filtered(lambda menu: menu.name in ['Agenda', 'Talk Proposals'])
menus.unlink()
@@ -68,35 +67,14 @@ class TestEventWebsiteTrack(TestWebsiteEventCommon):
self.assertFalse(event.website_track)
self.assertFalse(event.website_track_proposal)
self._assert_website_menus(event, set(['Location', 'Register', 'Community']))
self._assert_website_menus(event, ['Location', 'Register', 'Community'], menus_out=["Introduction", "Talks", "Agenda", "Talk Proposals"])
event.write({'website_track_proposal': True})
self.assertFalse(event.website_track)
self.assertTrue(event.website_track_proposal)
self._assert_website_menus(event, set(['Location', 'Register', 'Community', 'Talk Proposals']))
self._assert_website_menus(event, ['Location', 'Register', 'Community', 'Talk Proposals'], menus_out=["Introduction", "Talks", "Agenda"])
event.write({'website_track': True})
self.assertTrue(event.website_track)
self.assertTrue(event.website_track_proposal)
self._assert_website_menus(event, set(['Location', 'Register', 'Community', 'Talks', 'Agenda', 'Talk Proposals']))
@users('user_eventmanager')
def test_write_menu(self):
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.menu_id)
vals = {
'website_menu': True,
'community_menu': True,
'website_track': True,
'website_track_proposal': True,
}
if 'exhibitor_menu' in self.env['event.event']:
vals['exhibitor_menu'] = False
event.write(vals)
self._assert_website_menus(event)
self._assert_website_menus(event, ['Location', 'Register', 'Community', 'Talks', 'Agenda', 'Talk Proposals'], menus_out=["Introduction"])