@@ -107,6 +107,38 @@ def __init__(self, pool, request):
107
107
self .site = Site .objects .get_current (request )
108
108
self .draft_mode_active = use_draft (request )
109
109
110
+ def _get_cache_key (self , site_id ):
111
+ # This internal will change to a cached property on 3.5
112
+
113
+ prefix = getattr (settings , 'CMS_CACHE_PREFIX' , 'menu_cache_' )
114
+
115
+ key = '%smenu_nodes_%s_%s' % (prefix , self .language , site_id )
116
+
117
+ if self .request .user .is_authenticated ():
118
+ key += '_%s_user' % self .request .user .pk
119
+
120
+ if self .draft_mode_active :
121
+ key += ':draft'
122
+ else :
123
+ key += ':public'
124
+ return key
125
+
126
+ def _is_cached (self , site_id ):
127
+ # This internal will change to a cached property on 3.5
128
+
129
+ _internal_cache = '_is_cached_{}' .format (site_id )
130
+
131
+ if not hasattr (self , _internal_cache ):
132
+ cache_key = self ._get_cache_key (site_id = site_id )
133
+ db_cache_key_lookup = CacheKey .objects .filter (
134
+ key = cache_key ,
135
+ language = self .language ,
136
+ site = site_id ,
137
+ )
138
+ # Cache the lookup to avoid a query on every call to a menu tag
139
+ setattr (self , _internal_cache , db_cache_key_lookup .exists ())
140
+ return getattr (self , _internal_cache )
141
+
110
142
def _build_nodes (self , site_id ):
111
143
"""
112
144
This is slow. Caching must be used.
@@ -130,28 +162,17 @@ def _build_nodes(self, site_id):
130
162
"It will be removed in django CMS 3.5" ,
131
163
PendingDeprecationWarning
132
164
)
133
-
134
- if not site_id :
135
- # Backwards compatibility shim for < 3.5 projects
136
- # On 3.5, this method will change to no longer receive
137
- # a site id.
165
+ else :
138
166
site_id = self .site .pk
139
167
140
- prefix = getattr (settings , 'CMS_CACHE_PREFIX' , 'menu_cache_' )
141
-
142
- key = '%smenu_nodes_%s_%s' % (prefix , self .language , site_id )
143
-
144
- if self .request .user .is_authenticated ():
145
- key += '_%s_user' % self .request .user .pk
146
-
147
- if self .draft_mode_active :
148
- key += ':draft'
149
- else :
150
- key += ':public'
168
+ key = self ._get_cache_key (site_id )
151
169
152
170
cached_nodes = cache .get (key , None )
153
171
154
- if cached_nodes :
172
+ if cached_nodes and self ._is_cached (site_id ):
173
+ # Only use the cache if the key is present in the database.
174
+ # This prevents a condition where keys which have been removed
175
+ # from the database due to a change in content, are still used.
155
176
return cached_nodes
156
177
157
178
final_nodes = []
@@ -177,12 +198,17 @@ def _build_nodes(self, site_id):
177
198
final_nodes += _build_nodes_inner_for_one_menu (nodes , menu_class_name )
178
199
179
200
cache .set (key , final_nodes , get_cms_setting ('CACHE_DURATIONS' )['menus' ])
180
- # We need to have a list of the cache keys for languages and sites that
181
- # span several processes - so we follow the Django way and share through
182
- # the database. It's still cheaper than recomputing every time!
183
- # This way we can selectively invalidate per-site and per-language,
184
- # since the cache shared but the keys aren't
185
- CacheKey .objects .get_or_create (key = key , language = self .language , site = site_id )
201
+
202
+ if not self ._is_cached (site_id ):
203
+ # No need to invalidate the internal lookup cache,
204
+ # just set the value directly.
205
+ setattr (self , '_is_cached_{}' .format (site_id ), True )
206
+ # We need to have a list of the cache keys for languages and sites that
207
+ # span several processes - so we follow the Django way and share through
208
+ # the database. It's still cheaper than recomputing every time!
209
+ # This way we can selectively invalidate per-site and per-language,
210
+ # since the cache is shared but the keys aren't
211
+ CacheKey .objects .create (key = key , language = self .language , site = site_id )
186
212
return final_nodes
187
213
188
214
def _mark_selected (self , nodes ):
@@ -243,7 +269,7 @@ def get_renderer(self, request):
243
269
def discover_menus (self ):
244
270
if self .discovered :
245
271
return
246
- # FIXME: Remove in 3.4
272
+ # FIXME: Remove in 3.5
247
273
load ('menu' )
248
274
load ('cms_menus' )
249
275
from menus .modifiers import register
@@ -321,7 +347,7 @@ def register_menu(self, menu_cls):
321
347
322
348
if menu_cls .__module__ .split ('.' )[- 1 ] == 'menu' :
323
349
warnings .warn ('menu.py filename is deprecated, '
324
- 'and it will be removed in version 3.4 ; '
350
+ 'and it will be removed in version 3.5 ; '
325
351
'please rename it to cms_menus.py' , DeprecationWarning )
326
352
from menus .base import Menu
327
353
assert issubclass (menu_cls , Menu )
@@ -339,7 +365,7 @@ def register_modifier(self, modifier_class):
339
365
source_file = os .path .basename (inspect .stack ()[1 ][1 ])
340
366
if source_file == 'menu.py' :
341
367
warnings .warn ('menu.py filename is deprecated, '
342
- 'and it will be removed in version 3.4 ; '
368
+ 'and it will be removed in version 3.5 ; '
343
369
'please rename it to cms_menus.py' , DeprecationWarning )
344
370
from menus .base import Modifier
345
371
assert issubclass (modifier_class , Modifier )
@@ -368,7 +394,7 @@ def get_nodes_by_attribute(self, nodes, name, value):
368
394
def apply_modifiers (self , nodes , request , namespace = None , root_id = None ,
369
395
post_cut = False , breadcrumb = False ):
370
396
warnings .warn ('menu_pool.apply_modifiers is deprecated '
371
- 'and it will be removed in version 3.4 ; '
397
+ 'and it will be removed in version 3.5 ; '
372
398
'please use the menu renderer instead.' , DeprecationWarning )
373
399
renderer = self .get_renderer (request )
374
400
nodes = renderer .apply_modifiers (
@@ -383,7 +409,7 @@ def apply_modifiers(self, nodes, request, namespace=None, root_id=None,
383
409
def get_nodes (self , request , namespace = None , root_id = None , site_id = None ,
384
410
breadcrumb = False ):
385
411
warnings .warn ('menu_pool.get_nodes is deprecated '
386
- 'and it will be removed in version 3.4 ; '
412
+ 'and it will be removed in version 3.5 ; '
387
413
'please use the menu renderer instead.' , DeprecationWarning )
388
414
renderer = self .get_renderer (request )
389
415
nodes = renderer .get_nodes (
0 commit comments