menu.py 5.6 KB

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