menu.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. from django.forms import Media, MediaDefiningClass
  2. from django.forms.utils import flatatt
  3. from django.template.loader import render_to_string
  4. from django.utils.safestring import mark_safe
  5. from wagtail import hooks
  6. from wagtail.admin.ui.sidebar import LinkMenuItem as LinkMenuItemComponent
  7. from wagtail.admin.ui.sidebar import SubMenuItem as SubMenuItemComponent
  8. from wagtail.coreutils import cautious_slugify
  9. class MenuItem(metaclass=MediaDefiningClass):
  10. def __init__(
  11. self, label, url, name=None, classnames="", icon_name="", attrs=None, order=1000
  12. ):
  13. self.label = label
  14. self.url = url
  15. self.classnames = classnames
  16. self.icon_name = icon_name
  17. self.name = name or cautious_slugify(str(label))
  18. self.order = order
  19. if attrs:
  20. self.attr_string = flatatt(attrs)
  21. else:
  22. self.attr_string = ""
  23. def is_shown(self, request):
  24. """
  25. Whether this menu item should be shown for the given request; permission
  26. checks etc should go here. By default, menu items are shown all the time
  27. """
  28. return True
  29. def is_active(self, request):
  30. return request.path.startswith(str(self.url))
  31. def get_context(self, request):
  32. """Defines context for the template, overridable to use more data"""
  33. return {
  34. "name": self.name,
  35. "url": self.url,
  36. "classnames": self.classnames,
  37. "icon_name": self.icon_name,
  38. "attr_string": self.attr_string,
  39. "label": self.label,
  40. "active": self.is_active(request),
  41. }
  42. def render_html(self, request):
  43. context = self.get_context(request)
  44. return render_to_string(self.template, context, request=request)
  45. def render_component(self, request):
  46. return LinkMenuItemComponent(
  47. self.name,
  48. self.label,
  49. self.url,
  50. icon_name=self.icon_name,
  51. classnames=self.classnames,
  52. )
  53. class Menu:
  54. def __init__(self, register_hook_name, construct_hook_name=None):
  55. self.register_hook_name = register_hook_name
  56. self.construct_hook_name = construct_hook_name
  57. # _registered_menu_items will be populated on first access to the
  58. # registered_menu_items property. We can't populate it in __init__ because
  59. # we can't rely on all hooks modules to have been imported at the point that
  60. # we create the admin_menu and settings_menu instances
  61. self._registered_menu_items = None
  62. @property
  63. def registered_menu_items(self):
  64. if self._registered_menu_items is None:
  65. self._registered_menu_items = [
  66. fn() for fn in hooks.get_hooks(self.register_hook_name)
  67. ]
  68. return self._registered_menu_items
  69. def menu_items_for_request(self, request):
  70. items = [item for item in self.registered_menu_items if item.is_shown(request)]
  71. # provide a hook for modifying the menu, if construct_hook_name has been set
  72. if self.construct_hook_name:
  73. for fn in hooks.get_hooks(self.construct_hook_name):
  74. fn(request, items)
  75. return items
  76. def active_menu_items(self, request):
  77. return [
  78. item
  79. for item in self.menu_items_for_request(request)
  80. if item.is_active(request)
  81. ]
  82. @property
  83. def media(self):
  84. media = Media()
  85. for item in self.registered_menu_items:
  86. media += item.media
  87. return media
  88. def render_html(self, request):
  89. menu_items = self.menu_items_for_request(request)
  90. rendered_menu_items = []
  91. for item in sorted(menu_items, key=lambda i: i.order):
  92. rendered_menu_items.append(item.render_html(request))
  93. return mark_safe("".join(rendered_menu_items))
  94. def render_component(self, request):
  95. menu_items = self.menu_items_for_request(request)
  96. rendered_menu_items = []
  97. for item in sorted(menu_items, key=lambda i: i.order):
  98. rendered_menu_items.append(item.render_component(request))
  99. return rendered_menu_items
  100. class SubmenuMenuItem(MenuItem):
  101. """A MenuItem which wraps an inner Menu object"""
  102. def __init__(self, label, menu, **kwargs):
  103. self.menu = menu
  104. super().__init__(label, "#", **kwargs)
  105. def is_shown(self, request):
  106. # show the submenu if one or more of its children is shown
  107. return bool(self.menu.menu_items_for_request(request))
  108. def is_active(self, request):
  109. return bool(self.menu.active_menu_items(request))
  110. def render_component(self, request):
  111. return SubMenuItemComponent(
  112. self.name,
  113. self.label,
  114. self.menu.render_component(request),
  115. icon_name=self.icon_name,
  116. classnames=self.classnames,
  117. )
  118. class AdminOnlyMenuItem(MenuItem):
  119. """A MenuItem which is only shown to superusers"""
  120. def is_shown(self, request):
  121. return request.user.is_superuser
  122. admin_menu = Menu(
  123. register_hook_name="register_admin_menu_item",
  124. construct_hook_name="construct_main_menu",
  125. )
  126. settings_menu = Menu(
  127. register_hook_name="register_settings_menu_item",
  128. construct_hook_name="construct_settings_menu",
  129. )
  130. reports_menu = Menu(
  131. register_hook_name="register_reports_menu_item",
  132. construct_hook_name="construct_reports_menu",
  133. )