123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- from typing import List
- from flask import current_app, url_for
- from flask_babel import gettext as __
- from .api import BaseApi, expose
- from .basemanager import BaseManager
- from .security.decorators import permission_name, protect
- class MenuItem(object):
- def __init__(
- self, name, href="", icon="", label="", childs=None, baseview=None, cond=None
- ):
- self.name = name
- self.href = href
- self.icon = icon
- self.label = label
- self.childs = childs or []
- self.baseview = baseview
- self.cond = cond
- def should_render(self) -> bool:
- return bool(self.cond()) if self.cond is not None else True
- def get_url(self):
- if not self.href:
- if not self.baseview:
- return ""
- else:
- return url_for(f"{self.baseview.endpoint}.{self.baseview.default_view}")
- else:
- try:
- return url_for(self.href)
- except Exception:
- return self.href
- def __repr__(self):
- return self.name
- class Menu(object):
- def __init__(self, reverse=True, extra_classes=""):
- self.menu = []
- if reverse:
- extra_classes = extra_classes + "navbar-inverse"
- self.extra_classes = extra_classes
- @property
- def reverse(self):
- return "navbar-inverse" in self.extra_classes
- def get_list(self):
- return self.menu
- def get_flat_name_list(self, menu: "Menu" = None, result: List = None) -> List:
- menu = menu or self.menu
- result = result or []
- for item in menu:
- result.append(item.name)
- if item.childs:
- result.extend(self.get_flat_name_list(menu=item.childs, result=result))
- return result
- def get_data(self, menu=None):
- menu = menu or self.menu
- ret_list = []
- allowed_menus = current_app.appbuilder.sm.get_user_menu_access(
- self.get_flat_name_list()
- )
- for i, item in enumerate(menu):
- if not item.should_render():
- continue
- if item.name == "-" and not i == len(menu) - 1:
- ret_list.append("-")
- elif item.name not in allowed_menus:
- continue
- elif item.childs:
- ret_list.append(
- {
- "name": item.name,
- "icon": item.icon,
- "label": __(str(item.label)),
- "childs": self.get_data(menu=item.childs),
- }
- )
- else:
- ret_list.append(
- {
- "name": item.name,
- "icon": item.icon,
- "label": __(str(item.label)),
- "url": item.get_url(),
- }
- )
- return ret_list
- def find(self, name, menu=None):
- """
- Finds a menu item by name and returns it.
- :param name:
- The menu item name.
- """
- menu = menu or self.menu
- for i in menu:
- if i.name == name:
- return i
- else:
- if i.childs:
- ret_item = self.find(name, menu=i.childs)
- if ret_item:
- return ret_item
- def add_category(self, category, icon="", label="", parent_category=""):
- label = label or category
- if parent_category == "":
- self.menu.append(MenuItem(name=category, icon=icon, label=label))
- else:
- self.find(category).childs.append(
- MenuItem(name=category, icon=icon, label=label)
- )
- def add_link(
- self,
- name,
- href="",
- icon="",
- label="",
- category="",
- category_icon="",
- category_label="",
- baseview=None,
- cond=None,
- ):
- label = label or name
- category_label = category_label or category
- if category == "":
- self.menu.append(
- MenuItem(
- name=name,
- href=href,
- icon=icon,
- label=label,
- baseview=baseview,
- cond=cond,
- )
- )
- else:
- menu_item = self.find(category)
- if menu_item:
- new_menu_item = MenuItem(
- name=name,
- href=href,
- icon=icon,
- label=label,
- baseview=baseview,
- cond=cond,
- )
- menu_item.childs.append(new_menu_item)
- else:
- self.add_category(
- category=category, icon=category_icon, label=category_label
- )
- new_menu_item = MenuItem(
- name=name,
- href=href,
- icon=icon,
- label=label,
- baseview=baseview,
- cond=cond,
- )
- self.find(category).childs.append(new_menu_item)
- def add_separator(self, category="", cond=None):
- menu_item = self.find(category)
- if menu_item:
- menu_item.childs.append(MenuItem("-", cond=cond))
- else:
- raise Exception(
- "Menu separator does not have correct category {}".format(category)
- )
- class MenuApi(BaseApi):
- resource_name = "menu"
- openapi_spec_tag = "Menu"
- @expose("/", methods=["GET"])
- @protect(allow_browser_login=True)
- @permission_name("get")
- def get_menu_data(self):
- """An endpoint for retreiving the menu.
- ---
- get:
- description: >-
- Get the menu data structure.
- Returns a forest like structure with the menu the user has access to
- responses:
- 200:
- description: Get menu data
- content:
- application/json:
- schema:
- type: object
- properties:
- result:
- description: Menu items in a forest like data structure
- type: array
- items:
- type: object
- properties:
- name:
- description: >-
- The internal menu item name, maps to permission_name
- type: string
- label:
- description: Pretty name for the menu item
- type: string
- icon:
- description: Icon name to show for this menu item
- type: string
- url:
- description: The URL for the menu item
- type: string
- childs:
- type: array
- items:
- type: object
- 401:
- $ref: '#/components/responses/401'
- """
- return self.response(200, result=current_app.appbuilder.menu.get_data())
- class MenuApiManager(BaseManager):
- def register_views(self):
- if self.appbuilder.app.config.get("FAB_ADD_MENU_API", True):
- self.appbuilder.add_api(MenuApi)
|