From: Stephen Burrows Date: Tue, 21 Dec 2010 15:12:03 +0000 (-0500) Subject: Merge branch 'master' into navigation X-Git-Tag: philo-0.9~22^2~17^2~8 X-Git-Url: http://git.ithinksw.org/philo.git/commitdiff_plain/64e4cdefe89f642d349b5908a3bbaec76333e3b2?hp=-c Merge branch 'master' into navigation --- 64e4cdefe89f642d349b5908a3bbaec76333e3b2 diff --combined models/nodes.py index 6035148,de10ed1..5676b58 --- a/models/nodes.py +++ b/models/nodes.py @@@ -10,16 -10,13 +10,16 @@@ from django.template import add_to_buil from inspect import getargspec from philo.exceptions import MIDDLEWARE_NOT_CONFIGURED from philo.models.base import TreeEntity, Entity, QuerySetMapper, register_value_model +from philo.models.fields import JSONField from philo.utils import ContentTypeSubclassLimiter from philo.validators import RedirectValidator from philo.exceptions import ViewCanNotProvideSubpath, ViewDoesNotProvideSubpaths, AncestorDoesNotExist from philo.signals import view_about_to_render, view_finished_rendering +from mptt.templatetags.mptt_tags import cache_tree_children _view_content_type_limiter = ContentTypeSubclassLimiter(None) +DEFAULT_NAVIGATION_DEPTH = 3 class Node(TreeEntity): @@@ -51,46 -48,6 +51,46 @@@ except AncestorDoesNotExist, ViewDoesNotExist: return None + def get_navigation(self, depth=DEFAULT_NAVIGATION_DEPTH): + max_depth = depth + self.get_level() + tree = cache_tree_children(self.get_descendants(include_self=True).filter(level__lte=max_depth)) + + def get_nav(parent, nodes): + node_overrides = dict([(override.child.pk, override) for override in NodeNavigationOverride.objects.filter(parent=parent, child__in=nodes).select_related('child')]) + + navigation_list = [] + for node in nodes: + node._override = node_overrides.get(node.pk, None) + + if node._override: + if node._override.hide: + continue + navigation = node._override.get_navigation(node, max_depth) + else: + navigation = node.view.get_navigation(node, max_depth) + + if not node.is_leaf_node() and node.get_level() < max_depth: + children = navigation.get('children', []) + children += get_nav(node, node.get_children()) + navigation['children'] = children + + if 'children' in navigation: + navigation['children'].sort(cmp=lambda x,y: cmp(x['order'], y['order'])) + + navigation_list.append(navigation) + + return navigation_list + + navigation = get_nav(self.parent, tree) + root = navigation[0] + navigation = [root] + root['children'] + del(root['children']) + return navigation + + def save(self): + super(Node, self).save() + + class Meta: app_label = 'philo' @@@ -99,47 -56,6 +99,47 @@@ models.ForeignKey(Node, related_name='sites', null=True, blank=True).contribute_to_class(Site, 'root_node') +class NodeNavigationOverride(Entity): + parent = models.ForeignKey(Node, related_name="child_navigation_overrides", blank=True, null=True) + child = models.ForeignKey(Node, related_name="navigation_overrides") + + title = models.CharField(max_length=100, blank=True) + url = models.CharField(max_length=200, validators=[RedirectValidator()], blank=True) + order = models.PositiveSmallIntegerField(blank=True, null=True) + child_navigation = JSONField() + hide = models.BooleanField() + + def get_navigation(self, node, max_depth): + default = node.view.get_navigation(node, max_depth) + if self.url: + default['url'] = self.url + if self.title: + default['title'] = self.title + if self.order: + default['order'] = self.order + if isinstance(self.child_navigation, list) and node.get_level() < max_depth: + child_navigation = self.child_navigation[:] + + for child in child_navigation: + child['url'] = default['url'] + child['url'] + + if 'children' in default: + overridden = set([child['url'] for child in default['children']]) & set([child['url'] for child in self.child_navigation]) + if overridden: + for child in default[:]: + if child['url'] in overridden: + default.remove(child) + default['children'] += self.child_navigation + else: + default['children'] = self.child_navigation + return default + + class Meta: + ordering = ['order'] + unique_together = ('parent', 'child',) + app_label = 'philo' + + class View(Entity): nodes = generic.GenericRelation(Node, content_type_field='view_content_type', object_id_field='view_object_id') @@@ -175,22 -91,6 +175,22 @@@ def actually_render_to_response(self, request, extra_context=None): raise NotImplementedError('View subclasses must implement render_to_response.') + def get_navigation(self, node, max_depth): + """ + Subclasses should implement get_navigation to support auto-generated navigation. + max_depth is the deepest `level` that should be generated; node is the node that + is asking for the navigation. This method should return a dictionary of the form: + { + 'url': url, + 'title': title, + 'order': order, # None for no ordering. + 'children': [ # Optional + + ] + } + """ + raise NotImplementedError('View subclasses must implement get_navigation.') + class Meta: abstract = True @@@ -219,6 -119,14 +219,14 @@@ class MultiView(View) kwargs['extra_context'] = extra_context return view(request, *args, **kwargs) + def reverse(self, view_name, args=None, kwargs=None, node=None): + """Shortcut method to handle the common pattern of getting the absolute url for a multiview's + subpaths.""" + subpath = reverse(view_name, urlconf=self, args=args or [], kwargs=kwargs or {}) + if node is not None: + return '/%s/%s/' % (node.get_absolute_url().strip('/'), subpath.strip('/')) + return subpath + class Meta: abstract = True diff --combined templatetags/nodes.py index 744be4d,73492d4..c2dcd9a --- a/templatetags/nodes.py +++ b/templatetags/nodes.py @@@ -51,7 -51,9 +51,9 @@@ class NodeURLNode(template.Node) subpath = reverse(view_name, urlconf=node.view, args=args, kwargs=kwargs) except NoReverseMatch: if self.as_var is None: - raise + if settings.TEMPLATE_DEBUG: + raise + return settings.TEMPLATE_STRING_IF_INVALID else: if subpath[0] == '/': subpath = subpath[1:] @@@ -68,7 -70,7 +70,7 @@@ @register.tag(name='node_url') def do_node_url(parser, token): """ - {% node_url [for ] [as ] [as ] %} {% node_url with [for ] [as ] %} {% node_url [ [ ...] ] [for ] [as ] %} {% node_url [= [= ...] ] [for ] [as ]%} @@@ -112,84 -114,4 +114,84 @@@ args.append(parser.compile_filter(value)) return NodeURLNode(view_name=view_name, args=args, kwargs=kwargs, node=node, as_var=as_var) - return NodeURLNode(node=node, as_var=as_var) + return NodeURLNode(node=node, as_var=as_var) + + +class NavigationNode(template.Node): + def __init__(self, node=None, as_var=None): + self.as_var = as_var + self.node = node + + def render(self, context): + if 'request' not in context: + return settings.TEMPLATE_STRING_IF_INVALID + + if self.node: + node = self.node.resolve(context) + else: + node = context.get('node', None) + + if not node: + return settings.TEMPLATE_STRING_IF_INVALID + + try: + nav_root = node.attributes['navigation_root'] + except KeyError: + if settings.TEMPLATE_DEBUG: + raise + return settings.TEMPLATE_STRING_IF_INVALID + + # Should I get its override and check for a max depth override there? + navigation = nav_root.get_navigation() + + if self.as_var: + context[self.as_var] = navigation + return '' + + return self.compile(navigation, context['request'].path, nav_root.get_absolute_url(), nav_root.get_level(), nav_root.get_level() + 3) + + def compile(self, navigation, active_path, root_url, current_depth, max_depth): + compiled = "" + for item in navigation: + if item['url'] in active_path and (item['url'] != root_url or root_url == active_path): + compiled += "
  • " + else: + compiled += "
  • " + + if item['url']: + compiled += "" % item['url'] + + compiled += item['title'] + + if item['url']: + compiled += "" + + if 'children' in item and current_depth < max_depth: + compiled += "
      %s
    " % self.compile(item['children'], active_path, root_url, current_depth + 1, max_depth) + + compiled += "
  • " + return compiled + + +@register.tag(name='navigation') +def do_navigation(parser, token): + """ + {% navigation [for ] [as ] %} + """ + bits = token.split_contents() + tag = bits[0] + bits = bits[1:] + node = None + as_var = None + + if len(bits) >= 2 and bits[-2] == 'as': + as_var = bits[-1] + bits = bits[:-2] + + if len(bits) >= 2 and bits[-2] == 'for': + node = parser.compile_filter(bits[-1]) + bits = bits[-2] + + if bits: + raise template.TemplateSyntaxError('`%s` template tag expects the syntax {%% %s [for ] [as ] %}' % (tag, tag)) + return NavigationNode(node, as_var)