menus.menu_pool: 125 total statements, 0.0% covered

Generated: Wed 2013-03-13 10:33 CET

Source file: /media/Envs/Envs/filer-gallery/lib/python2.7/site-packages/menus/menu_pool.py

Stats: 0 executed, 114 missed, 11 excluded, 72 ignored

  1. # -*- coding: utf-8 -*-
  2. from cms.utils.django_load import load
  3. from django.conf import 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. from menus.exceptions import NamespaceAllreadyRegistered
  8. from menus.models import CacheKey
  9. import copy
  10. def _build_nodes_inner_for_one_menu(nodes, menu_class_name):
  11. '''
  12. This is an easier to test "inner loop" building the menu tree structure
  13. for one menu (one language, one site)
  14. '''
  15. done_nodes = {} # Dict of node.id:Node
  16. final_nodes = []
  17. # This is to prevent infinite loops - we need to compare the number of
  18. # times we see a specific node to "something", and for the time being,
  19. # it's the total number of nodes
  20. list_total_length = len(nodes)
  21. while nodes:
  22. # For when the node has a parent_id but we haven't seen it yet.
  23. # We must not append it to the final list in this case!
  24. should_add_to_final_list = True
  25. node = nodes.pop(0)
  26. # Increment the "seen" counter for this specific node.
  27. node._counter = getattr(node,'_counter',0) + 1
  28. # Implicit namespacing by menu.__name__
  29. if not node.namespace:
  30. node.namespace = menu_class_name
  31. if node.namespace not in done_nodes:
  32. # We need to create the namespace dict to avoid KeyErrors
  33. done_nodes[node.namespace] = {}
  34. # If we have seen the parent_id already...
  35. if node.parent_id in done_nodes[node.namespace] :
  36. # Implicit parent namespace by menu.__name__
  37. if not node.parent_namespace:
  38. node.parent_namespace = menu_class_name
  39. parent = done_nodes[node.namespace][node.parent_id]
  40. parent.children.append(node)
  41. node.parent = parent
  42. # If it has a parent_id but we haven't seen it yet...
  43. elif node.parent_id:
  44. # We check for infinite loops here, by comparing the number of
  45. # times we "saw" this node to the number of nodes in the list
  46. if node._counter < list_total_length:
  47. nodes.append(node)
  48. # Never add this node to the final list until it has a real
  49. # parent (node.parent)
  50. should_add_to_final_list = False
  51. if should_add_to_final_list:
  52. final_nodes.append(node)
  53. # add it to the "seen" list
  54. done_nodes[node.namespace][node.id] = node
  55. return final_nodes
  56. class MenuPool(object):
  57. def __init__(self):
  58. self.menus = {}
  59. self.modifiers = []
  60. self.discovered = False
  61. def discover_menus(self):
  62. if self.discovered:
  63. return
  64. load('menu')
  65. from menus.modifiers import register
  66. register()
  67. self.discovered = True
  68. def clear(self, site_id=None, language=None, all=False):
  69. '''
  70. This invalidates the cache for a given menu (site_id and language)
  71. '''
  72. if all:
  73. cache_keys = CacheKey.objects.get_keys()
  74. else:
  75. cache_keys = CacheKey.objects.get_keys(site_id, language)
  76. to_be_deleted = cache_keys.distinct().values_list('key', flat=True)
  77. cache.delete_many(to_be_deleted)
  78. cache_keys.delete()
  79. def register_menu(self, menu):
  80. from menus.base import Menu
  81. assert issubclass(menu, Menu)
  82. if menu.__name__ in self.menus.keys():
  83. raise NamespaceAllreadyRegistered(
  84. "[%s] a menu with this name is already registered" % menu.__name__)
  85. self.menus[menu.__name__] = menu()
  86. def register_modifier(self, modifier_class):
  87. from menus.base import Modifier
  88. assert issubclass(modifier_class, Modifier)
  89. if not modifier_class in self.modifiers:
  90. self.modifiers.append(modifier_class)
  91. def _build_nodes(self, request, site_id):
  92. """
  93. This is slow. Caching must be used.
  94. One menu is built per language and per site.
  95. Namespaces: they are ID prefixes to avoid node ID clashes when plugging
  96. multiple trees together.
  97. - We iterate on the list of nodes.
  98. - We store encountered nodes in a dict (with namespaces):
  99. done_nodes[<namespace>][<node's id>] = node
  100. - When a node has a parent defined, we lookup that parent in done_nodes
  101. if it's found:
  102. set the node as the node's parent's child (re-read this)
  103. else:
  104. the node is put at the bottom of the list
  105. """
  106. # Cache key management
  107. lang = get_language()
  108. prefix = getattr(settings, "CMS_CACHE_PREFIX", "menu_cache_")
  109. key = "%smenu_nodes_%s_%s" % (prefix, lang, site_id)
  110. if request.user.is_authenticated():
  111. key += "_%s_user" % request.user.pk
  112. cached_nodes = cache.get(key, None)
  113. if cached_nodes:
  114. return cached_nodes
  115. final_nodes = []
  116. for menu_class_name in self.menus:
  117. nodes = self.menus[menu_class_name].get_nodes(request)
  118. # nodes is a list of navigation nodes (page tree in cms + others)
  119. final_nodes += _build_nodes_inner_for_one_menu(nodes, menu_class_name)
  120. cache.set(key, final_nodes, settings.CMS_CACHE_DURATIONS['menus'])
  121. # We need to have a list of the cache keys for languages and sites that
  122. # span several processes - so we follow the Django way and share through
  123. # the database. It's still cheaper than recomputing every time!
  124. # This way we can selectively invalidate per-site and per-language,
  125. # since the cache shared but the keys aren't
  126. CacheKey.objects.get_or_create(key=key, language=lang, site=site_id)
  127. return final_nodes
  128. def apply_modifiers(self, nodes, request, namespace=None, root_id=None, post_cut=False, breadcrumb=False):
  129. if not post_cut:
  130. nodes = self._mark_selected(request, nodes)
  131. for cls in self.modifiers:
  132. inst = cls()
  133. nodes = inst.modify(request, nodes, namespace, root_id, post_cut, breadcrumb)
  134. return nodes
  135. def get_nodes(self, request, namespace=None, root_id=None, site_id=None, breadcrumb=False):
  136. self.discover_menus()
  137. if not site_id:
  138. site_id = Site.objects.get_current().pk
  139. nodes = self._build_nodes(request, site_id)
  140. nodes = copy.deepcopy(nodes)
  141. nodes = self.apply_modifiers(nodes, request, namespace, root_id, post_cut=False, breadcrumb=breadcrumb)
  142. return nodes
  143. def _mark_selected(self, request, nodes):
  144. sel = None
  145. for node in nodes:
  146. node.sibling = False
  147. node.ancestor = False
  148. node.descendant = False
  149. node.selected = False
  150. if node.get_absolute_url() == request.path[:len(node.get_absolute_url())]:
  151. if sel:
  152. if len(node.get_absolute_url()) > len(sel.get_absolute_url()):
  153. sel = node
  154. else:
  155. sel = node
  156. else:
  157. node.selected = False
  158. if sel:
  159. sel.selected = True
  160. return nodes
  161. def get_menus_by_attribute(self, name, value):
  162. self.discover_menus()
  163. found = []
  164. for menu in self.menus.items():
  165. if hasattr(menu[1], name) and getattr(menu[1], name, None) == value:
  166. found.append((menu[0], menu[1].name))
  167. return found
  168. def get_nodes_by_attribute(self, nodes, name, value):
  169. found = []
  170. for node in nodes:
  171. if node.attr.get(name, None) == value:
  172. found.append(node)
  173. return found
  174. menu_pool = MenuPool()