from django.core.exceptions import ValidationError
from django.core.urlresolvers import NoReverseMatch
from django.core.validators import RegexValidator, MinValueValidator
from django.db import models
from django.forms.models import model_to_dict
from django.core.exceptions import ValidationError
from django.core.urlresolvers import NoReverseMatch
from django.core.validators import RegexValidator, MinValueValidator
from django.db import models
from django.forms.models import model_to_dict
-from philo.models import TreeEntity, Node, TreeManager, Entity, TargetURLModel
-from philo.validators import RedirectValidator
-from UserDict import DictMixin
+
+from philo.models.base import TreeEntity, TreeManager, Entity
+from philo.models.nodes import Node, TargetURLModel
-class NavigationQuerySetMapper(object, DictMixin):
- """This class exists to prevent setting of items in the navigation cache through node.navigation."""
+class NavigationMapper(object, DictMixin):
+ """
+ The :class:`NavigationMapper` is a dictionary-like object which allows easy fetching of the root items of a navigation for a node according to a key. The fetching goes through the :class:`NavigationManager` and can thus take advantage of the navigation cache. A :class:`NavigationMapper` instance will be available on each node instance as :attr:`Node.navigation` if :mod:`~philo.contrib.shipherd` is in the :setting:`INSTALLED_APPS`
+
+ """
This subclass will trigger general cache clearing for Navigation.objects when a mass
update or deletion is performed. As there is no convenient way to iterate over the
changed or deleted instances, there's no way to be more precise about what gets cleared.
This subclass will trigger general cache clearing for Navigation.objects when a mass
update or deletion is performed. As there is no convenient way to iterate over the
changed or deleted instances, there's no way to be more precise about what gets cleared.
"""
def update(self, *args, **kwargs):
super(NavigationCacheQuerySet, self).update(*args, **kwargs)
"""
def update(self, *args, **kwargs):
super(NavigationCacheQuerySet, self).update(*args, **kwargs)
- # Since navigation is going to be hit frequently and changed
- # relatively infrequently, cache it. Analogous to contenttypes.
+ """
+ Since navigation on a site will be hit frequently, is relatively costly to compute, and is changed relatively infrequently, the NavigationManager maintains a cache which maps nodes to navigations.
+
+ """
return NavigationCacheQuerySet(self.model, using=self._db)
def get_cache_for(self, node, update_targets=True):
return NavigationCacheQuerySet(self.model, using=self._db)
def get_cache_for(self, node, update_targets=True):
return self.__class__._cache[self.db][node]
def has_cache_for(self, 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):
return self.db in self.__class__._cache and node in self.__class__._cache[self.db]
def create_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.
+ """Clear the cache for the :class:`.Node` and all its descendants. The navigation for this node has probably changed, and it isn't worth it to figure out which descendants were actually affected by this."""
- # 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.
+ """Manually updates the target nodes for the :class:`.Node`'s cache in case something's changed there. This is a less complex operation than rebuilding the :class:`.Node`'s cache."""
self.__class__._cache.pop(self.db, None)
class Navigation(Entity):
self.__class__._cache.pop(self.db, None)
class Navigation(Entity):
node = models.ForeignKey(Node, related_name='navigation_set', help_text="Be available as navigation for this node.")
node = models.ForeignKey(Node, related_name='navigation_set', help_text="Be available as navigation for this node.")
key = models.CharField(max_length=255, validators=[RegexValidator("\w+")], help_text="Must contain one or more alphanumeric characters or underscores.", db_index=True)
key = models.CharField(max_length=255, validators=[RegexValidator("\w+")], help_text="Must contain one or more alphanumeric characters or underscores.", db_index=True)
depth = models.PositiveSmallIntegerField(default=DEFAULT_NAVIGATION_DEPTH, validators=[MinValueValidator(1)], help_text="Defines the maximum display depth of this navigation.")
def __init__(self, *args, **kwargs):
depth = models.PositiveSmallIntegerField(default=DEFAULT_NAVIGATION_DEPTH, validators=[MinValueValidator(1)], help_text="Defines the maximum display depth of this navigation.")
def __init__(self, *args, **kwargs):
return NavigationCacheQuerySet(self.model, using=self._db)
class NavigationItem(TreeEntity, TargetURLModel):
return NavigationCacheQuerySet(self.model, using=self._db)
class NavigationItem(TreeEntity, TargetURLModel):
navigation = models.ForeignKey(Navigation, blank=True, null=True, related_name='roots', help_text="Be a root in this navigation tree.")
navigation = models.ForeignKey(Navigation, blank=True, null=True, related_name='roots', help_text="Be a root in this navigation tree.")
order = models.PositiveSmallIntegerField(default=0)
def __init__(self, *args, **kwargs):
order = models.PositiveSmallIntegerField(default=0)
def __init__(self, *args, **kwargs):
raise ValidationError("Exactly one of `parent` and `navigation` must be defined.")
def is_active(self, request):
raise ValidationError("Exactly one of `parent` and `navigation` must be defined.")
def is_active(self, request):
if self.target_url == request.path:
# Handle the `default` case where the target_url and requested path
# are identical.
if self.target_url == request.path:
# Handle the `default` case where the target_url and requested path
# are identical.
for child in self.get_children():
if child.is_active(request) or child.has_active_descendants(request):
return True
for child in self.get_children():
if child.is_active(request) or child.has_active_descendants(request):
return True