Permalink
Browse files

Added a template tag for displaying part of a topic hierarchy.

  • Loading branch information...
1 parent 52b0f8f commit 55094baaaad78c59dd83b3aae629c081da336d0e Malcolm Tredinnick committed Jun 7, 2010
No changes.
@@ -0,0 +1,70 @@
+from django import template
+from django.db import models
+from django.utils.html import escape
+
+register = template.Library()
+
+class TreeTrunkNode(template.Node):
+ """
+ Render the first few levels of a topic tree as an unordered HTML list.
+ """
+ def __init__(self, model_name, levels=2):
+ super(TreeTrunkNode, self).__init__()
+ app_name, model_name = model_name.rsplit(".", 1)
+ self.model = models.get_model(app_name, model_name)
+ if self.model is None:
+ raise template.TemplateSyntaxError("Bad app or model name: %s" %
+ model_name)
+ self.levels = levels
+
+ def render(self, context):
+ current_level = 0
+ pieces = [u"<ul>"]
+ first = True
+ for node in self.model.tree.filter(level__lt=self.levels):
+ diff = node.level - current_level
+ if diff == 0:
+ if first:
+ first = False
+ else:
+ pieces.append(u"</li>")
+ pieces.append(u"<li>%s" % escape(node.name))
+ elif diff > 0:
+ pieces.append(u"<ul>\n<li>%s" % escape(node.name))
+ current_level += 1
+ else:
+ pieces.append(u"</li></ul></li>\n<li>%s" % escape(node.name))
+ current_level -= 1
+ if len(pieces) == 1:
+ # No content in the tree means no output.
+ return u""
+ while current_level:
+ pieces.append(u"</li></ul>")
+ current_level -= 1
+ pieces.append(u"</li>\n</ul>")
+ return u"\n".join(pieces)
+
+
+@register.tag
+def treetrunk(dummy, token):
+ """
+ Called as {% treetops app.SomeModel N %} to display the first N levels of
+ the Topic-derived tree class, SomeModel. The number of levels (N) can be
+ omitted and defaults to 2 (root nodes and their children).
+ """
+ bits = token.split_contents()
+ if len(bits) == 3:
+ try:
+ level = int(bits[2])
+ if level <= 0:
+ raise ValueError
+ except ValueError:
+ raise template.TemplateSyntaxError("Level argument ('%s') wasn't "
+ "a positive integer." % bits[2])
+ elif len(bits) == 2:
+ level = 2
+ else:
+ raise template.TemplateSyntaxError("Invalid number of arguments (%d, "
+ "expected 1 or 2)." % (len(bits) - 1))
+ return TreeTrunkNode(bits[1], level)
+
@@ -1,8 +1,10 @@
import unittest
-from acacia.tests import test_models
+from acacia.tests import test_models, test_templatetags
def suite():
suite = unittest.defaultTestLoader.loadTestsFromModule(test_models)
+ suite.addTests(unittest.defaultTestLoader. \
+ loadTestsFromModule(test_templatetags))
return suite
@@ -0,0 +1,176 @@
+# coding: utf-8
+"""
+Tests for the custom template acacia template tags.
+"""
+
+from django import template, test
+
+from acacia import models
+
+
+def setup_from_node_strings(nodes):
+ """
+ Convert a list of node names into a populated model hierarchy.
+ """
+ for node in nodes:
+ models.Topic.objects.get_or_create_by_full_name(node)
+
+def convert(template_string):
+ compiled = template.Template(template_string)
+ return compiled.render(template.Context({}))
+
+class TreeTrunkErrorTests(test.TestCase):
+ def test_invalid_app_name(self):
+ self.failUnlessRaises(template.TemplateSyntaxError, convert,
+ "{% load acacia %}{% treetrunk bad.Topic %}")
+
+ def test_invalid_model_name(self):
+ self.failUnlessRaises(template.TemplateSyntaxError, convert,
+ "{% load acacia %}{% treetrunk acacia.BadName %}")
+
+ def test_non_integer_level_value(self):
+ self.failUnlessRaises(template.TemplateSyntaxError, convert,
+ "{% load acacia %}{% treetrunk acacia.Topic bad %}")
+
+ def test_negative_level_value(self):
+ self.failUnlessRaises(template.TemplateSyntaxError, convert,
+ "{% load acacia %}{% treetrunk acacia.Topic -1 %}")
+
+ def test_zero_level_value(self):
+ self.failUnlessRaises(template.TemplateSyntaxError, convert,
+ "{% load acacia %}{% treetrunk acacia.Topic 0 %}")
+
+ def test_too_many_arguments(self):
+ self.failUnlessRaises(template.TemplateSyntaxError, convert,
+ "{% load acacia %}{% treetrunk acacia.Topic 2 extra %}")
+
+ def test_too_few_arguments(self):
+ self.failUnlessRaises(template.TemplateSyntaxError, convert,
+ "{% load acacia %}{% treetrunk %}")
+
+
+class TreeTrunkMiscTests(test.TestCase):
+ def test_empty_tree(self):
+ """
+ No content should result in nothing being inserted into the template,
+ not an empty unordered list element.
+ """
+ self.failIf(convert("{% load acacia %}{% treetrunk acacia.Topic %}"))
+
+class TreeTrunkSingleRootTests(test.TestCase):
+ def setUp(self):
+ # pylint: disable-msg=C0103
+ nodes = [
+ "root1/child1/grandchild1",
+ "root1/child1/grandchild2",
+ "root1/child2/grandchild1",
+ "root1/child2/grandchild2",
+ "root1/child3/grandchild1",
+ ]
+ setup_from_node_strings(nodes)
+
+ def test_single_level(self):
+ output = convert("{% load acacia %}{% treetrunk acacia.Topic 1 %}")
+ output = output.replace("\n", "")
+ expected = """
+ <ul>
+ <li>root1</li>
+ </ul>""".replace("\n", "").replace(" ", "")
+ self.failUnlessEqual(output, expected, "\nGot %s\n\nExpected %s" %
+ (output, expected))
+
+ def test_two_levels(self):
+ output = convert("{% load acacia %}{% treetrunk acacia.Topic 2 %}")
+ output = output.replace("\n", "")
+ expected = """
+ <ul>
+ <li>root1
+ <ul><li>child1</li>
+ <li>child2</li>
+ <li>child3</li>
+ </ul></li>
+ </ul>""".replace("\n", "").replace(" ", "")
+ self.failUnlessEqual(output, expected, "\nGot %s\n\nExpected %s" %
+ (output, expected))
+
+ def test_three_levels(self):
+ output = convert("{% load acacia %}{% treetrunk acacia.Topic 3 %}")
+ output = output.replace("\n", "")
+ expected = """
+ <ul>
+ <li>root1
+ <ul><li>child1
+ <ul><li>grandchild1</li>
+ <li>grandchild2</li></ul></li>
+ <li>child2
+ <ul><li>grandchild1</li>
+ <li>grandchild2</li></ul></li>
+ <li>child3
+ <ul><li>grandchild1</li></ul></li>
+ </ul></li>
+ </ul>""".replace("\n", "").replace(" ", "")
+ self.failUnlessEqual(output, expected, "\nGot %s\n\nExpected %s" %
+ (output, expected))
+
+ def test_more_levels_than_nodes(self):
+ output = convert("{% load acacia %}{% treetrunk acacia.Topic 10 %}")
+ output = output.replace("\n", "")
+ expected = """
+ <ul>
+ <li>root1
+ <ul><li>child1
+ <ul><li>grandchild1</li>
+ <li>grandchild2</li></ul></li>
+ <li>child2
+ <ul><li>grandchild1</li>
+ <li>grandchild2</li></ul></li>
+ <li>child3
+ <ul><li>grandchild1</li></ul></li>
+ </ul></li>
+ </ul>""".replace("\n", "").replace(" ", "")
+ self.failUnlessEqual(output, expected, "\nGot %s\n\nExpected %s" %
+ (output, expected))
+
+ def test_default_level(self):
+ output = convert("{% load acacia %}{% treetrunk acacia.Topic %}")
+ output = output.replace("\n", "")
+ expected = """
+ <ul>
+ <li>root1
+ <ul><li>child1</li>
+ <li>child2</li>
+ <li>child3</li>
+ </ul></li>
+ </ul>""".replace("\n", "").replace(" ", "")
+ self.failUnlessEqual(output, expected, "\nGot %s\n\nExpected %s" %
+ (output, expected))
+
+
+class TreeTrunkFullContentTests(test.TestCase):
+ def setUp(self):
+ # pylint: disable-msg=C0103
+ nodes = [
+ "root1/child1/grandchild1",
+ "root1/child1/grandchild2",
+ "root1/child1/grandchild3",
+ "root1/child2/grandchild1",
+ "root1/child2/grandchild2",
+ "root1/child3/grandchild1",
+ "root1/child4",
+ "root2/child1/grandchild1",
+ "root2/child2/grandchild1",
+ "root2/child2/grandchild2/other",
+ "root3"
+ ]
+ setup_from_node_strings(nodes)
+
+# TODO: Tests
+# - multiple roots in tree; trees of different depths
+# - default level value
+# - level value less than depth of tree
+# - level value more than depth of tree
+# - level value equal to depth of tree
+# - Tag with unicode name containing HTML special characters (e.g. < and
+# &amp;)
+
+

0 comments on commit 55094ba

Please sign in to comment.