/menus/menu_pool.py

https://github.com/daonb/django-cms
Python | 162 lines | 144 code | 15 blank | 3 comment | 46 complexity | 1716fd5fb9bcd23cb63a6cb69794de3c MD5 | raw file
  1. from django.conf import settings
  2. from menus.exceptions import NamespaceAllreadyRegistered
  3. from menus import settings as menus_settings
  4. from django.contrib.sites.models import Site
  5. from django.core.cache import cache
  6. from django.utils.translation import get_language
  7. import copy
  8. def lex_cache_key(key):
  9. """
  10. Returns the language and site ID a cache key is related to.
  11. """
  12. return key.rsplit('_', 2)[1:]
  13. class MenuPool(object):
  14. def __init__(self):
  15. self.menus = {}
  16. self.modifiers = []
  17. self.discovered = False
  18. self.cache_keys = set()
  19. def discover_menus(self):
  20. if self.discovered:
  21. return
  22. for app in settings.INSTALLED_APPS:
  23. __import__(app, {}, {}, ['menu'])
  24. from menus.modifiers import register
  25. register()
  26. self.discovered = True
  27. def clear(self, site_id=None, language=None):
  28. def relevance_test(keylang, keysite):
  29. sok = not site_id
  30. lok = not language
  31. if site_id and (site_id == keysite or site_id == int(keysite)):
  32. sok = True
  33. if language and language == keylang:
  34. lok = True
  35. return lok and sok
  36. to_be_deleted = []
  37. for key in self.cache_keys:
  38. keylang, keysite = lex_cache_key(key)
  39. if relevance_test(keylang, keysite):
  40. to_be_deleted.append(key)
  41. cache.delete_many(to_be_deleted)
  42. self.cache_keys.difference_update(to_be_deleted)
  43. def register_menu(self, menu):
  44. from menus.base import Menu
  45. assert issubclass(menu, Menu)
  46. if menu.__name__ in self.menus.keys():
  47. raise NamespaceAllreadyRegistered, "[%s] a menu with this name is already registered" % menu.__name__
  48. self.menus[menu.__name__] = menu()
  49. def register_modifier(self, modifier_class):
  50. from menus.base import Modifier
  51. assert issubclass(modifier_class, Modifier)
  52. if not modifier_class in self.modifiers:
  53. self.modifiers.append(modifier_class)
  54. def _build_nodes(self, request, site_id):
  55. lang = get_language()
  56. prefix = getattr(settings, "CMS_CACHE_PREFIX", "menu_cache_")
  57. key = "%smenu_nodes_%s_%s" % (prefix, lang, site_id)
  58. self.cache_keys.add(key)
  59. cached_nodes = cache.get(key, None)
  60. if cached_nodes:
  61. return cached_nodes
  62. final_nodes = []
  63. for ns in self.menus:
  64. try:
  65. nodes = self.menus[ns].get_nodes(request)
  66. except:
  67. raise
  68. last = None
  69. for node in nodes:
  70. if not node.namespace:
  71. node.namespace = ns
  72. if node.parent_id:
  73. if not node.parent_namespace:
  74. node.parent_namespace = ns
  75. found = False
  76. if last:
  77. n = last
  78. while n:
  79. if n.namespace == node.namespace and n.id == node.parent_id:
  80. node.parent = n
  81. found = True
  82. n = None
  83. elif n.parent:
  84. n = n.parent
  85. else:
  86. n = None
  87. if not found:
  88. for n in nodes:
  89. if n.namespace == node.namespace and n.id == node.parent_id:
  90. node.parent = n
  91. found = True
  92. if found:
  93. node.parent.children.append(node)
  94. else:
  95. continue
  96. final_nodes.append(node)
  97. last = node
  98. duration = getattr(settings, "MENU_CACHE_DURATION", 60*60)
  99. cache.set(key, final_nodes, duration)
  100. return final_nodes
  101. def apply_modifiers(self, nodes, request, namespace=None, root_id=None, post_cut=False, breadcrumb=False):
  102. if not post_cut:
  103. nodes = self._mark_selected(request, nodes)
  104. for cls in self.modifiers:
  105. inst = cls()
  106. nodes = inst.modify(request, nodes, namespace, root_id, post_cut, breadcrumb)
  107. return nodes
  108. def get_nodes(self, request, namespace=None, root_id=None, site_id=None, breadcrumb=False):
  109. self.discover_menus()
  110. if not site_id:
  111. site_id = Site.objects.get_current().pk
  112. nodes = self._build_nodes(request, site_id)
  113. nodes = copy.deepcopy(nodes)
  114. nodes = self.apply_modifiers(nodes, request, namespace, root_id, post_cut=False, breadcrumb=breadcrumb)
  115. return nodes
  116. def _mark_selected(self, request, nodes):
  117. sel = None
  118. for node in nodes:
  119. node.sibling = False
  120. node.ancestor = False
  121. node.descendant = False
  122. node.selected = False
  123. if node.get_absolute_url() == request.path[:len(node.get_absolute_url())]:
  124. if sel:
  125. if len(node.get_absolute_url()) > len(sel.get_absolute_url()):
  126. sel = node
  127. else:
  128. sel = node
  129. else:
  130. node.selected = False
  131. if sel:
  132. sel.selected = True
  133. return nodes
  134. def get_menus_by_attribute(self, name, value):
  135. self.discover_menus()
  136. found = []
  137. for menu in self.menus.items():
  138. if hasattr(menu[1], name) and getattr(menu[1], name, None) == value:
  139. found.append((menu[0], menu[1].name))
  140. return found
  141. def get_nodes_by_attribute(self, nodes, name, value):
  142. found = []
  143. for node in nodes:
  144. if node.attr.get(name, None) == value:
  145. found.append(node)
  146. return found
  147. menu_pool = MenuPool()