2018-11-29 01:39:20 -06:00
|
|
|
# -*- coding: utf-8; -*-
|
|
|
|
################################################################################
|
|
|
|
#
|
|
|
|
# Rattail -- Retail Software Framework
|
2021-02-01 11:57:12 -06:00
|
|
|
# Copyright © 2010-2021 Lance Edgar
|
2018-11-29 01:39:20 -06:00
|
|
|
#
|
|
|
|
# This file is part of Rattail.
|
|
|
|
#
|
|
|
|
# Rattail is free software: you can redistribute it and/or modify it under the
|
|
|
|
# terms of the GNU General Public License as published by the Free Software
|
|
|
|
# Foundation, either version 3 of the License, or (at your option) any later
|
|
|
|
# version.
|
|
|
|
#
|
|
|
|
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
|
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
|
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
|
|
# details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License along with
|
|
|
|
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#
|
|
|
|
################################################################################
|
|
|
|
"""
|
|
|
|
App Menus
|
|
|
|
"""
|
|
|
|
|
|
|
|
from __future__ import unicode_literals, absolute_import
|
|
|
|
|
|
|
|
from rattail.core import Object
|
|
|
|
from rattail.util import import_module_path
|
|
|
|
|
|
|
|
|
|
|
|
class MenuGroup(Object):
|
|
|
|
title = None
|
|
|
|
items = None
|
2021-02-01 11:57:12 -06:00
|
|
|
is_menu = True
|
2018-11-29 14:51:57 -06:00
|
|
|
is_link = False
|
2018-11-29 01:39:20 -06:00
|
|
|
|
|
|
|
|
|
|
|
class MenuItem(Object):
|
|
|
|
title = None
|
|
|
|
url = None
|
2018-11-29 14:00:11 -06:00
|
|
|
target = None
|
2018-11-29 14:51:57 -06:00
|
|
|
is_link = True
|
2021-02-01 11:57:12 -06:00
|
|
|
is_menu = False
|
|
|
|
is_sep = False
|
|
|
|
|
|
|
|
|
|
|
|
class MenuItemMenu(Object):
|
|
|
|
title = None
|
|
|
|
items = None
|
|
|
|
is_menu = True
|
2018-11-29 14:00:11 -06:00
|
|
|
is_sep = False
|
|
|
|
|
|
|
|
|
|
|
|
class MenuSeparator(object):
|
2021-02-01 11:57:12 -06:00
|
|
|
is_menu = False
|
2018-11-29 14:00:11 -06:00
|
|
|
is_sep = True
|
2018-11-29 01:39:20 -06:00
|
|
|
|
|
|
|
|
|
|
|
def make_simple_menus(request):
|
|
|
|
"""
|
|
|
|
Build the main menu list for the app.
|
|
|
|
"""
|
|
|
|
menus_module = import_module_path(
|
|
|
|
request.rattail_config.require('tailbone', 'menus'))
|
|
|
|
|
|
|
|
if not hasattr(menus_module, 'simple_menus') or not callable(menus_module.simple_menus):
|
|
|
|
raise RuntimeError("module does not have a simple_menus() callable: {}".format(menus_module))
|
|
|
|
|
|
|
|
# collect "simple" menus definition, but must refine that somewhat to
|
|
|
|
# produce our final menus
|
|
|
|
raw_menus = menus_module.simple_menus(request)
|
2021-02-01 11:57:12 -06:00
|
|
|
mark_allowed(request, raw_menus)
|
2018-11-29 01:39:20 -06:00
|
|
|
final_menus = []
|
|
|
|
for topitem in raw_menus:
|
|
|
|
|
2021-02-01 11:57:12 -06:00
|
|
|
if topitem['allowed']:
|
2018-11-29 14:00:11 -06:00
|
|
|
|
2021-02-01 11:57:12 -06:00
|
|
|
if topitem.get('type') == 'link':
|
|
|
|
final_menus.append(make_menu_entry(topitem))
|
2018-11-29 14:00:11 -06:00
|
|
|
|
2021-02-01 11:57:12 -06:00
|
|
|
else: # assuming 'menu' type
|
2018-11-29 01:39:20 -06:00
|
|
|
|
2018-11-29 14:51:57 -06:00
|
|
|
menu_items = []
|
2021-02-01 11:57:12 -06:00
|
|
|
for item in topitem['items']:
|
|
|
|
if not item['allowed']:
|
|
|
|
continue
|
|
|
|
|
|
|
|
# nested submenu
|
|
|
|
if item.get('type') == 'menu':
|
|
|
|
submenu_items = []
|
|
|
|
for subitem in item['items']:
|
|
|
|
if subitem['allowed']:
|
|
|
|
submenu_items.append(make_menu_entry(subitem))
|
|
|
|
menu_items.append(MenuItemMenu(
|
|
|
|
title=item['title'],
|
|
|
|
items=submenu_items))
|
|
|
|
|
|
|
|
elif item.get('type') == 'sep':
|
|
|
|
# we only want to add a sep, *if* we already have some
|
|
|
|
# menu items (i.e. there is something to separate)
|
|
|
|
# *and* the last menu item is not a sep (avoid doubles)
|
2018-11-29 14:51:57 -06:00
|
|
|
if menu_items and not menu_items[-1].is_sep:
|
2021-02-01 11:57:12 -06:00
|
|
|
menu_items.append(make_menu_entry(item))
|
2018-11-29 14:51:57 -06:00
|
|
|
|
2021-02-01 11:57:12 -06:00
|
|
|
else: # standard menu item
|
|
|
|
menu_items.append(make_menu_entry(item))
|
2018-11-29 14:51:57 -06:00
|
|
|
|
|
|
|
# remove final separator if present
|
|
|
|
if menu_items and menu_items[-1].is_sep:
|
|
|
|
menu_items.pop()
|
|
|
|
|
|
|
|
# only add if we wound up with something
|
2021-02-01 11:57:12 -06:00
|
|
|
assert menu_items
|
2018-11-29 14:51:57 -06:00
|
|
|
if menu_items:
|
2021-02-01 11:57:12 -06:00
|
|
|
final_menus.append(MenuGroup(
|
|
|
|
title=topitem['title'],
|
|
|
|
items=menu_items))
|
2018-11-29 01:39:20 -06:00
|
|
|
|
|
|
|
return final_menus
|
2021-02-01 11:57:12 -06:00
|
|
|
|
|
|
|
|
|
|
|
def make_menu_entry(item):
|
|
|
|
"""
|
|
|
|
Convert a simple menu entry dict, into a proper menu-related object, for
|
|
|
|
use in constructing final menu.
|
|
|
|
"""
|
|
|
|
# separator
|
|
|
|
if item.get('type') == 'sep':
|
|
|
|
return MenuSeparator()
|
|
|
|
|
|
|
|
# standard menu item
|
|
|
|
return MenuItem(
|
|
|
|
title=item['title'],
|
|
|
|
url=item['url'],
|
|
|
|
target=item.get('target'))
|
|
|
|
|
|
|
|
|
|
|
|
def is_allowed(request, item):
|
|
|
|
"""
|
|
|
|
Logic to determine if a given menu item is "allowed" for current user.
|
|
|
|
"""
|
|
|
|
perm = item.get('perm')
|
|
|
|
if perm:
|
|
|
|
return request.has_perm(perm)
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def mark_allowed(request, menus):
|
|
|
|
"""
|
|
|
|
Traverse the menu set, and mark each item as "allowed" (or not) based on
|
|
|
|
current user permissions.
|
|
|
|
"""
|
|
|
|
for topitem in menus:
|
|
|
|
|
|
|
|
if topitem.get('type', 'menu') == 'menu':
|
|
|
|
topitem['allowed'] = False
|
|
|
|
|
|
|
|
for item in topitem['items']:
|
|
|
|
|
|
|
|
if item.get('type') == 'menu':
|
|
|
|
for subitem in item['items']:
|
|
|
|
subitem['allowed'] = is_allowed(request, subitem)
|
|
|
|
|
|
|
|
item['allowed'] = False
|
|
|
|
for subitem in item['items']:
|
|
|
|
if subitem['allowed'] and subitem.get('type') != 'sep':
|
|
|
|
item['allowed'] = True
|
|
|
|
break
|
|
|
|
|
|
|
|
else:
|
|
|
|
item['allowed'] = is_allowed(request, item)
|
|
|
|
|
|
|
|
for item in topitem['items']:
|
|
|
|
if item['allowed'] and item.get('type') != 'sep':
|
|
|
|
topitem['allowed'] = True
|
|
|
|
break
|