4 from django import template
5 from django.conf import settings
6 from django.db import connection
7 from django.template import loader
8 from django.template.loaders import cached
9 from django.test import TestCase
10 from django.test.utils import setup_test_template_loader
12 from philo.contrib.penfield.models import Blog, BlogView, BlogEntry
13 from philo.exceptions import AncestorDoesNotExist
14 from philo.models import Node, Page, Template
17 class TemplateTestCase(TestCase):
18 fixtures = ['test_fixtures.json']
20 def test_templates(self):
21 "Tests to make sure that embed behaves with complex includes and extends"
22 template_tests = self.get_template_tests()
24 # Register our custom template loader. Shamelessly cribbed from django/tests/regressiontests/templates/tests.py:384.
25 cache_loader = setup_test_template_loader(
26 dict([(name, t[0]) for name, t in template_tests.iteritems()]),
27 use_cached_loader=True,
31 tests = template_tests.items()
34 # Turn TEMPLATE_DEBUG off, because tests assume that.
35 old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
37 # Set TEMPLATE_STRING_IF_INVALID to a known string.
38 old_invalid = settings.TEMPLATE_STRING_IF_INVALID
39 expected_invalid_str = 'INVALID'
42 for name, vals in tests:
43 xx, context, result = vals
45 test_template = loader.get_template(name)
46 output = test_template.render(template.Context(context))
48 exc_type, exc_value, exc_tb = sys.exc_info()
49 if exc_type != result:
50 tb = '\n'.join(traceback.format_exception(exc_type, exc_value, exc_tb))
51 failures.append("Template test %s -- FAILED. Got %s, exception: %s\n%s" % (name, exc_type, exc_value, tb))
54 failures.append("Template test %s -- FAILED. Expected %r, got %r" % (name, result, output))
57 settings.TEMPLATE_DEBUG = old_td
58 settings.TEMPLATE_STRING_IF_INVALID = old_invalid
59 loader.template_source_loaders = old_template_loaders
61 self.assertEqual(failures, [], "Tests failed:\n%s\n%s" % ('-'*70, ("\n%s\n" % ('-'*70)).join(failures)))
64 def get_template_tests(self):
66 # 'template_name': ('template contents', 'context dict', 'expected string output' or Exception class)
67 blog = Blog.objects.all()[0]
69 # EMBED INCLUSION HANDLING
71 'embed01': ('{{ embedded.title|safe }}', {'embedded': blog}, blog.title),
72 'embed02': ('{{ embedded.title|safe }}{{ var1 }}{{ var2 }}', {'embedded': blog}, blog.title),
73 'embed03': ('{{ embedded.title|safe }} is a lie!', {'embedded': blog}, '%s is a lie!' % blog.title),
75 # Simple template structure with embed
76 'simple01': ('{% embed penfield.blog with "embed01" %}{% embed penfield.blog 1 %}Simple{% block one %}{% endblock %}', {'blog': blog}, '%sSimple' % blog.title),
77 'simple02': ('{% extends "simple01" %}', {}, '%sSimple' % blog.title),
78 'simple03': ('{% embed penfield.blog with "embed000" %}', {}, settings.TEMPLATE_STRING_IF_INVALID),
79 'simple04': ('{% embed penfield.blog 1 %}', {}, settings.TEMPLATE_STRING_IF_INVALID),
80 'simple05': ('{% embed penfield.blog with "embed01" %}{% embed blog %}', {'blog': blog}, blog.title),
83 'kwargs01': ('{% embed penfield.blog with "embed02" %}{% embed penfield.blog 1 var1="hi" var2=lo %}', {'lo': 'lo'}, '%shilo' % blog.title),
86 'filters01': ('{% embed penfield.blog with "embed02" %}{% embed penfield.blog 1 var1=hi|first var2=lo|slice:"3" %}', {'hi': ["These", "words"], 'lo': 'lower'}, '%sTheselow' % blog.title),
87 'filters02': ('{% embed penfield.blog with "embed01" %}{% embed penfield.blog entry %}', {'entry': 1}, blog.title),
90 'block01': ('{% block one %}Hello{% endblock %}', {}, 'Hello'),
91 'block02': ('{% extends "simple01" %}{% block one %}{% embed penfield.blog 1 %}{% endblock %}', {}, "%sSimple%s" % (blog.title, blog.title)),
92 'block03': ('{% extends "simple01" %}{% embed penfield.blog with "embed03" %}{% block one %}{% embed penfield.blog 1 %}{% endblock %}', {}, "%sSimple%s is a lie!" % (blog.title, blog.title)),
95 'block-include01': ('{% extends "simple01" %}{% embed penfield.blog with "embed03" %}{% block one %}{% include "simple01" %}{% embed penfield.blog 1 %}{% endblock %}', {}, "%sSimple%sSimple%s is a lie!" % (blog.title, blog.title, blog.title)),
96 'block-include02': ('{% extends "simple01" %}{% block one %}{% include "simple04" %}{% embed penfield.blog with "embed03" %}{% include "simple04" %}{% embed penfield.blog 1 %}{% endblock %}', {}, "%sSimple%s%s is a lie!%s is a lie!" % (blog.title, blog.title, blog.title, blog.title)),
98 # Tests for more complex situations...
99 'complex01': ('{% block one %}{% endblock %}complex{% block two %}{% endblock %}', {}, 'complex'),
100 'complex02': ('{% extends "complex01" %}', {}, 'complex'),
101 'complex03': ('{% extends "complex02" %}{% embed penfield.blog with "embed01" %}', {}, 'complex'),
102 'complex04': ('{% extends "complex03" %}{% block one %}{% embed penfield.blog 1 %}{% endblock %}', {}, '%scomplex' % blog.title),
103 'complex05': ('{% extends "complex03" %}{% block one %}{% include "simple04" %}{% endblock %}', {}, '%scomplex' % blog.title),
107 class NodeURLTestCase(TestCase):
108 """Tests the features of the node_url template tag."""
110 fixtures = ['test_fixtures.json']
113 if 'south' in settings.INSTALLED_APPS:
114 from south.management.commands.migrate import Command
116 command.handle(all_apps=True)
119 ("{% node_url %}", "/root/second/"),
120 ("{% node_url for node2 %}", "/root/second2/"),
121 ("{% node_url as hello %}<p>{{ hello|slice:'1:' }}</p>", "<p>root/second/</p>"),
122 ("{% node_url for nodes|first %}", "/root/"),
123 ("{% node_url with entry %}", settings.TEMPLATE_STRING_IF_INVALID),
124 ("{% node_url with entry for node2 %}", "/root/second2/2010/10/20/first-entry"),
125 ("{% node_url with tag for node2 %}", "/root/second2/tags/test-tag/"),
126 ("{% node_url with date for node2 %}", "/root/second2/2010/10/20"),
127 ("{% node_url entries_by_day year=date|date:'Y' month=date|date:'m' day=date|date:'d' for node2 as goodbye %}<em>{{ goodbye|upper }}</em>", "<em>/ROOT/SECOND2/2010/10/20</em>"),
128 ("{% node_url entries_by_month year=date|date:'Y' month=date|date:'m' for node2 %}", "/root/second2/2010/10"),
129 ("{% node_url entries_by_year year=date|date:'Y' for node2 %}", "/root/second2/2010/"),
132 nodes = Node.objects.all()
133 blog = Blog.objects.all()[0]
135 self.context = template.Context({
136 'node': nodes.get(slug='second'),
137 'node2': nodes.get(slug='second2'),
139 'entry': BlogEntry.objects.all()[0],
140 'tag': blog.entry_tags.all()[0],
141 'date': blog.entry_dates['day'][0]
144 def test_nodeurl(self):
145 for string, result in self.templates:
146 self.assertEqual(template.Template(string).render(self.context), result)
148 class TreePathTestCase(TestCase):
150 fixtures = ['test_fixtures.json']
153 if 'south' in settings.INSTALLED_APPS:
154 from south.management.commands.migrate import Command
156 command.handle(all_apps=True)
158 def assertQueryLimit(self, max, expected_result, *args, **kwargs):
159 # As a rough measure of efficiency, limit the number of queries required for a given operation.
160 settings.DEBUG = True
161 call = kwargs.pop('callable', Node.objects.get_with_path)
163 queries = len(connection.queries)
164 if isinstance(expected_result, type) and issubclass(expected_result, Exception):
165 self.assertRaises(expected_result, call, *args, **kwargs)
167 self.assertEqual(call(*args, **kwargs), expected_result)
168 queries = len(connection.queries) - queries
170 raise AssertionError('"%d" unexpectedly not less than or equal to "%s"' % (queries, max))
172 settings.DEBUG = False
174 def test_get_with_path(self):
175 root = Node.objects.get(slug='root')
176 third = Node.objects.get(slug='third')
177 second2 = Node.objects.get(slug='second2')
178 fifth = Node.objects.get(slug='fifth')
179 e = Node.DoesNotExist
182 self.assertQueryLimit(0, root, '', root=root)
183 self.assertQueryLimit(0, e, '')
184 self.assertQueryLimit(0, (root, None), '', root=root, absolute_result=False)
187 self.assertQueryLimit(1, third, 'root/second/third')
188 self.assertQueryLimit(1, third, 'second/third', root=root)
189 self.assertQueryLimit(1, third, 'root//////second/third///')
191 self.assertQueryLimit(1, e, 'root/secont/third')
192 self.assertQueryLimit(1, e, 'second/third')
194 # Non-absolute result (binary search)
195 self.assertQueryLimit(2, (second2, 'sub/path/tail'), 'root/second2/sub/path/tail', absolute_result=False)
196 self.assertQueryLimit(3, (second2, 'sub/'), 'root/second2/sub/', absolute_result=False)
197 self.assertQueryLimit(2, e, 'invalid/path/1/2/3/4/5/6/7/8/9/1/2/3/4/5/6/7/8/9/0', absolute_result=False)
198 self.assertQueryLimit(1, (root, None), 'root', absolute_result=False)
199 self.assertQueryLimit(2, (second2, None), 'root/second2', absolute_result=False)
200 self.assertQueryLimit(3, (third, None), 'root/second/third', absolute_result=False)
203 self.assertQueryLimit(1, (second2, None), 'second2', root=root, absolute_result=False)
204 self.assertQueryLimit(2, (third, None), 'second/third', root=root, absolute_result=False)
206 # Preserve trailing slash
207 self.assertQueryLimit(2, (second2, 'sub/path/tail/'), 'root/second2/sub/path/tail/', absolute_result=False)
209 # Speed increase for leaf nodes - should this be tested?
210 self.assertQueryLimit(1, (fifth, 'sub/path/tail/len/five'), 'root/second/third/fourth/fifth/sub/path/tail/len/five', absolute_result=False)
212 def test_get_path(self):
213 root = Node.objects.get(slug='root')
214 root2 = Node.objects.get(slug='root')
215 third = Node.objects.get(slug='third')
216 second2 = Node.objects.get(slug='second2')
217 fifth = Node.objects.get(slug='fifth')
218 e = AncestorDoesNotExist
220 self.assertQueryLimit(0, 'root', callable=root.get_path)
221 self.assertQueryLimit(0, '', root2, callable=root.get_path)
222 self.assertQueryLimit(1, 'root/second/third', callable=third.get_path)
223 self.assertQueryLimit(1, 'second/third', root, callable=third.get_path)
224 self.assertQueryLimit(1, e, third, callable=second2.get_path)
225 self.assertQueryLimit(1, '? - ?', root, ' - ', 'title', callable=third.get_path)