- _cache = {}
-
- def get_queryset(self):
- return NavigationCacheQuerySet(self.model, using=self._db)
-
- def get_cache_for(self, node, update_targets=True):
- created = False
- if not self.has_cache_for(node):
- self.create_cache_for(node)
- created = True
-
- if update_targets and not created:
- self.update_targets_for(node)
-
- return self.__class__._cache[self.db][node]
-
- def has_cache_for(self, node):
- return self.db in self.__class__._cache and node in self.__class__._cache[self.db]
-
- def create_cache_for(self, node):
- "This method loops through the nodes ancestors and caches all unique navigation keys."
- ancestors = node.get_ancestors(ascending=True, include_self=True)
-
- nodes_to_cache = []
-
- for node in ancestors:
- if self.has_cache_for(node):
- cache = self.get_cache_for(node).copy()
- break
- else:
- nodes_to_cache.insert(0, node)
- else:
- cache = {}
-
- for node in nodes_to_cache:
- cache = cache.copy()
- cache.update(self._build_cache_for(node))
- self.__class__._cache.setdefault(self.db, {})[node] = cache
-
- def _build_cache_for(self, node):
- cache = {}
- tree_id_attr = NavigationItem._mptt_meta.tree_id_attr
- level_attr = NavigationItem._mptt_meta.level_attr
-
- for navigation in node.navigation_set.all():
- tree_ids = navigation.roots.values_list(tree_id_attr)
- items = list(NavigationItem.objects.filter(**{'%s__in' % tree_id_attr: tree_ids, '%s__lt' % level_attr: navigation.depth}).order_by('order', 'lft'))
-
- root_items = []
-
- for item in items:
- item._is_cached = True
-
- if not hasattr(item, '_cached_children'):
- item._cached_children = []
-
- if item.parent:
- # alternatively, if I don't want to force it to a list, I could keep track of
- # instances where the parent hasn't yet been met and do this step later for them.
- # delayed action.
- item.parent = items[items.index(item.parent)]
- if not hasattr(item.parent, '_cached_children'):
- item.parent._cached_children = []
- item.parent._cached_children.append(item)
- else:
- root_items.append(item)
-
- cache[navigation.key] = {
- 'navigation': navigation,
- 'root_items': root_items,
- 'items': items
- }
-
- return cache
-
- def clear_cache_for(self, node):
- # Clear the cache for this node and all its descendants. The
- # navigation for this node has probably changed, and for now,
- # it isn't worth it to only clear the descendants actually
- # affected by this.
- if not self.has_cache_for(node):
- # Already cleared.
- return
-
- descendants = node.get_descendants(include_self=True)
- cache = self.__class__._cache[self.db]
- for node in descendants:
- cache.pop(node, None)
-
- def update_targets_for(self, node):
- # Manually update a cache's target nodes in case something's changed there.
- # This should be a less complex operation than reloading the models each
- # time. Not as good as selective updates... but not much to be done
- # about that. TODO: Benchmark it.
- caches = self.__class__._cache[self.db][node].values()
-
- target_pks = set()
-
- for cache in caches:
- target_pks |= set([item.target_node_id for item in cache['items']])
-
- # A distinct query is not strictly necessary. TODO: benchmark the efficiency
- # with/without distinct.
- targets = list(Node.objects.filter(pk__in=target_pks).distinct())
-
- for cache in caches:
- for item in cache['items']:
- if item.target_node_id:
- item.target_node = targets[targets.index(item.target_node)]