Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 198 lines (174 sloc) 7.876 kb
9966d47 @chrisglass Source encoding changes for menus app
chrisglass authored
1 # -*- coding: utf-8 -*-
5a595fb @ojii Moved plugin processors to a seperate file
ojii authored
2 from cms.utils.django_load import load
5c9aa1f @digi604 imports now working, show_menu displays nothing yet
digi604 authored
3 from django.conf import settings
03a78d9 @digi604 menu test cases added
digi604 authored
4 from django.contrib.sites.models import Site
76a0cd0 @digi604 menu now uses thread save cache for menu nodes
digi604 authored
5 from django.core.cache import cache
6122aa2 @digi604 allows the menus to work under non i18n conditions
digi604 authored
6 from django.utils.translation import get_language
0bb503a @ojii Use import_module in menu_pool
ojii authored
7 from menus.exceptions import NamespaceAllreadyRegistered
f193c4d @chrisglass - First try to have a process-safe caching mechanism for menu trees
chrisglass authored
8 from menus.models import CacheKey
252d5ce @ojii added CMS_CACHE_PREFIX setting to prefix the menu cache keys
ojii authored
9 import copy
5c9aa1f @digi604 imports now working, show_menu displays nothing yet
digi604 authored
10
035cb53 @ojii Added test for mark_descendants
ojii authored
11 def _build_nodes_inner_for_one_menu(nodes, menu_class_name):
12 '''
13 This is an easier to test "inner loop" building the menu tree structure
14 for one menu (one language, one site)
15 '''
16 done_nodes = {} # Dict of node.id:Node
17 final_nodes = []
18
19 # This is to prevent infinite loops - we need to compare the number of
20 # times we see a specific node to "something", and for the time being,
21 # it's the total number of nodes
22 list_total_length = len(nodes)
23
24 while nodes:
25 # For when the node has a parent_id but we haven't seen it yet.
26 # We must not append it to the final list in this case!
27 should_add_to_final_list = True
28
29 node = nodes.pop(0)
30
31 # Increment the "seen" counter for this specific node.
32 node._counter = getattr(node,'_counter',0) + 1
33
34 # Implicit namespacing by menu.__name__
35 if not node.namespace:
36 node.namespace = menu_class_name
37 if node.namespace not in done_nodes:
38 # We need to create the namespace dict to avoid KeyErrors
39 done_nodes[node.namespace] = {}
40
41 # If we have seen the parent_id already...
42 if node.parent_id in done_nodes[node.namespace] :
43 # Implicit parent namespace by menu.__name__
44 if not node.parent_namespace:
45 node.parent_namespace = menu_class_name
46 parent = done_nodes[node.namespace][node.parent_id]
47 parent.children.append(node)
48 node.parent = parent
49 # If it has a parent_id but we haven't seen it yet...
50 elif node.parent_id:
51 # We check for infinite loops here, by comparing the number of
52 # times we "saw" this node to the number of nodes in the list
53 if node._counter < list_total_length:
54 nodes.append(node)
55 # Never add this node to the final list until it has a real
56 # parent (node.parent)
57 should_add_to_final_list = False
58
59 if should_add_to_final_list:
60 final_nodes.append(node)
61 # add it to the "seen" list
62 done_nodes[node.namespace][node.id] = node
63 return final_nodes
64
5c9aa1f @digi604 imports now working, show_menu displays nothing yet
digi604 authored
65 class MenuPool(object):
66 def __init__(self):
67 self.menus = {}
68 self.modifiers = []
69 self.discovered = False
70
71 def discover_menus(self):
72 if self.discovered:
5a595fb @ojii Moved plugin processors to a seperate file
ojii authored
73 return
74 load('menu')
bda49b2 @digi604 menu_levels working. nav-extenders correctly hiding if not assigned
digi604 authored
75 from menus.modifiers import register
76 register()
5c9aa1f @digi604 imports now working, show_menu displays nothing yet
digi604 authored
77 self.discovered = True
27b30c2 @digi604 show menu below id working
digi604 authored
78
fd25f65 @jezdez Added can_view permission and re-enabled permissions caching again.
jezdez authored
79 def clear(self, site_id=None, language=None, all=False):
f193c4d @chrisglass - First try to have a process-safe caching mechanism for menu trees
chrisglass authored
80 '''
81 This invalidates the cache for a given menu (site_id and language)
82 '''
14b3f41 Merge remote branch 'jezdez/permissions-merge' into viewpermission
Tobias von Klipstein authored
83 if all:
84 cache_keys = CacheKey.objects.get_keys()
85 else:
86 cache_keys = CacheKey.objects.get_keys(site_id, language)
e204eac fixed bug that causes creation of thousands duplicated CacheKey records
Alexey Subbotin authored
87 to_be_deleted = cache_keys.distinct().values_list('key', flat=True)
252d5ce @ojii added CMS_CACHE_PREFIX setting to prefix the menu cache keys
ojii authored
88 cache.delete_many(to_be_deleted)
f193c4d @chrisglass - First try to have a process-safe caching mechanism for menu trees
chrisglass authored
89 cache_keys.delete()
5c9aa1f @digi604 imports now working, show_menu displays nothing yet
digi604 authored
90
1b16cd4 @digi604 monster commit for menu refactor
digi604 authored
91 def register_menu(self, menu):
5c9aa1f @digi604 imports now working, show_menu displays nothing yet
digi604 authored
92 from menus.base import Menu
93 assert issubclass(menu, Menu)
1b16cd4 @digi604 monster commit for menu refactor
digi604 authored
94 if menu.__name__ in self.menus.keys():
0ea37ba @jezdez A bit of cleanup (PEP8, consistency, dead code).
jezdez authored
95 raise NamespaceAllreadyRegistered(
96 "[%s] a menu with this name is already registered" % menu.__name__)
1b16cd4 @digi604 monster commit for menu refactor
digi604 authored
97 self.menus[menu.__name__] = menu()
0ea37ba @jezdez A bit of cleanup (PEP8, consistency, dead code).
jezdez authored
98
1b16cd4 @digi604 monster commit for menu refactor
digi604 authored
99 def register_modifier(self, modifier_class):
100 from menus.base import Modifier
101 assert issubclass(modifier_class, Modifier)
2d19d5c @digi604 all tests pass, nav extenders fully working now
digi604 authored
102 if not modifier_class in self.modifiers:
103 self.modifiers.append(modifier_class)
0ea37ba @jezdez A bit of cleanup (PEP8, consistency, dead code).
jezdez authored
104
03a78d9 @digi604 menu test cases added
digi604 authored
105 def _build_nodes(self, request, site_id):
4c5a147 @chrisglass - Renumbered menu test methods (there were 2 "15"s)
chrisglass authored
106 """
107 This is slow. Caching must be used.
108 One menu is built per language and per site.
109
110 Namespaces: they are ID prefixes to avoid node ID clashes when plugging
111 multiple trees together.
112
113 - We iterate on the list of nodes.
114 - We store encountered nodes in a dict (with namespaces):
115 done_nodes[<namespace>][<node's id>] = node
116 - When a node has a parent defined, we lookup that parent in done_nodes
117 if it's found:
118 set the node as the node's parent's child (re-read this)
119 else:
120 the node is put at the bottom of the list
121 """
122 # Cache key management
6122aa2 @digi604 allows the menus to work under non i18n conditions
digi604 authored
123 lang = get_language()
5b90253 @digi604 made the menu app really standalone
digi604 authored
124 prefix = getattr(settings, "CMS_CACHE_PREFIX", "menu_cache_")
125 key = "%smenu_nodes_%s_%s" % (prefix, lang, site_id)
fd25f65 @jezdez Added can_view permission and re-enabled permissions caching again.
jezdez authored
126 if request.user.is_authenticated():
127 key += "_%s_user" % request.user.pk
76a0cd0 @digi604 menu now uses thread save cache for menu nodes
digi604 authored
128 cached_nodes = cache.get(key, None)
129 if cached_nodes:
130 return cached_nodes
4c5a147 @chrisglass - Renumbered menu test methods (there were 2 "15"s)
chrisglass authored
131
76a0cd0 @digi604 menu now uses thread save cache for menu nodes
digi604 authored
132 final_nodes = []
4c5a147 @chrisglass - Renumbered menu test methods (there were 2 "15"s)
chrisglass authored
133 for menu_class_name in self.menus:
134 nodes = self.menus[menu_class_name].get_nodes(request)
135 # nodes is a list of navigation nodes (page tree in cms + others)
035cb53 @ojii Added test for mark_descendants
ojii authored
136 final_nodes += _build_nodes_inner_for_one_menu(nodes, menu_class_name)
0621c9b @jezdez Combined the various cache duration settings in a new CMS_CACHE_DURATION...
jezdez authored
137 cache.set(key, final_nodes, settings.CMS_CACHE_DURATIONS['menus'])
f193c4d @chrisglass - First try to have a process-safe caching mechanism for menu trees
chrisglass authored
138 # We need to have a list of the cache keys for languages and sites that
139 # span several processes - so we follow the Django way and share through
140 # the database. It's still cheaper than recomputing every time!
141 # This way we can selectively invalidate per-site and per-language,
142 # since the cache shared but the keys aren't
e204eac fixed bug that causes creation of thousands duplicated CacheKey records
Alexey Subbotin authored
143 CacheKey.objects.get_or_create(key=key, language=lang, site=site_id)
76a0cd0 @digi604 menu now uses thread save cache for menu nodes
digi604 authored
144 return final_nodes
0ea37ba @jezdez A bit of cleanup (PEP8, consistency, dead code).
jezdez authored
145
bda49b2 @digi604 menu_levels working. nav-extenders correctly hiding if not assigned
digi604 authored
146 def apply_modifiers(self, nodes, request, namespace=None, root_id=None, post_cut=False, breadcrumb=False):
147 if not post_cut:
148 nodes = self._mark_selected(request, nodes)
2d19d5c @digi604 all tests pass, nav extenders fully working now
digi604 authored
149 for cls in self.modifiers:
150 inst = cls()
bda49b2 @digi604 menu_levels working. nav-extenders correctly hiding if not assigned
digi604 authored
151 nodes = inst.modify(request, nodes, namespace, root_id, post_cut, breadcrumb)
6e71206 @digi604 show menu working. marking of nodes working. modifiers working. cutlevel...
digi604 authored
152 return nodes
0ea37ba @jezdez A bit of cleanup (PEP8, consistency, dead code).
jezdez authored
153
bda49b2 @digi604 menu_levels working. nav-extenders correctly hiding if not assigned
digi604 authored
154 def get_nodes(self, request, namespace=None, root_id=None, site_id=None, breadcrumb=False):
2d19d5c @digi604 all tests pass, nav extenders fully working now
digi604 authored
155 self.discover_menus()
03a78d9 @digi604 menu test cases added
digi604 authored
156 if not site_id:
157 site_id = Site.objects.get_current().pk
158 nodes = self._build_nodes(request, site_id)
6e71206 @digi604 show menu working. marking of nodes working. modifiers working. cutlevel...
digi604 authored
159 nodes = copy.deepcopy(nodes)
38225bb @digi604 testsuite working again
digi604 authored
160 nodes = self.apply_modifiers(nodes, request, namespace, root_id, post_cut=False, breadcrumb=breadcrumb)
6e71206 @digi604 show menu working. marking of nodes working. modifiers working. cutlevel...
digi604 authored
161 return nodes
0ea37ba @jezdez A bit of cleanup (PEP8, consistency, dead code).
jezdez authored
162
03a78d9 @digi604 menu test cases added
digi604 authored
163 def _mark_selected(self, request, nodes):
65331d8 @digi604 softroots working
digi604 authored
164 sel = None
6e71206 @digi604 show menu working. marking of nodes working. modifiers working. cutlevel...
digi604 authored
165 for node in nodes:
03a78d9 @digi604 menu test cases added
digi604 authored
166 node.sibling = False
167 node.ancestor = False
168 node.descendant = False
65331d8 @digi604 softroots working
digi604 authored
169 node.selected = False
e0771cb @stefanfoulis invisible nodes can be selected, fixed menu and language chooser testcas...
stefanfoulis authored
170 if node.get_absolute_url() == request.path[:len(node.get_absolute_url())]:
65331d8 @digi604 softroots working
digi604 authored
171 if sel:
172 if len(node.get_absolute_url()) > len(sel.get_absolute_url()):
173 sel = node
174 else:
175 sel = node
6e71206 @digi604 show menu working. marking of nodes working. modifiers working. cutlevel...
digi604 authored
176 else:
177 node.selected = False
65331d8 @digi604 softroots working
digi604 authored
178 if sel:
179 sel.selected = True
6e71206 @digi604 show menu working. marking of nodes working. modifiers working. cutlevel...
digi604 authored
180 return nodes
0ea37ba @jezdez A bit of cleanup (PEP8, consistency, dead code).
jezdez authored
181
38225bb @digi604 testsuite working again
digi604 authored
182 def get_menus_by_attribute(self, name, value):
6967eea @digi604 apphooks working, nav extenders as well
digi604 authored
183 self.discover_menus()
38225bb @digi604 testsuite working again
digi604 authored
184 found = []
bda49b2 @digi604 menu_levels working. nav-extenders correctly hiding if not assigned
digi604 authored
185 for menu in self.menus.items():
38225bb @digi604 testsuite working again
digi604 authored
186 if hasattr(menu[1], name) and getattr(menu[1], name, None) == value:
187 found.append((menu[0], menu[1].name))
188 return found
0ea37ba @jezdez A bit of cleanup (PEP8, consistency, dead code).
jezdez authored
189
38225bb @digi604 testsuite working again
digi604 authored
190 def get_nodes_by_attribute(self, nodes, name, value):
191 found = []
192 for node in nodes:
193 if node.attr.get(name, None) == value:
194 found.append(node)
195 return found
0ea37ba @jezdez A bit of cleanup (PEP8, consistency, dead code).
jezdez authored
196
5c9aa1f @digi604 imports now working, show_menu displays nothing yet
digi604 authored
197 menu_pool = MenuPool()
Something went wrong with that request. Please try again.