Skip to content
Browse files

Fixed issue #1 -- Added ability to automatically load the template ta…

…g libraries that are used during the first pass of rendering.
  • Loading branch information...
1 parent b91d3ac commit e0552688432cbf59fa293ed64df4f668db1b7e62 @jezdez jezdez committed Dec 7, 2010
Showing with 84 additions and 31 deletions.
  1. +4 −2 example/templates/base.html
  2. +9 −8 phased/templatetags/phased_tags.py
  3. +15 −10 phased/tests.py
  4. +56 −11 phased/utils.py
View
6 example/templates/base.html
@@ -1,4 +1,4 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+{% load i18n %}<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
@@ -10,7 +10,9 @@
<div id="body">
{% load phased_tags %}
{% phased %}
- Hi, {{ user.username }}
+ {% blocktrans with user.username as username %}
+ Hi, {{ username }}
+ {% endblocktrans %}
{% endphased %}
{% block body %}
<div class="content_title">
View
17 phased/templatetags/phased_tags.py
@@ -4,7 +4,8 @@
from django.utils.encoding import smart_str
from phased import settings
-from phased.utils import pickle_context, flatten_context, backup_csrf_token
+from phased.utils import (pickle_context, flatten_context,
+ backup_csrf_token, pickle_components, find_components)
register = Library()
@@ -34,9 +35,10 @@ class PhasedNode(Node):
block with pickled context, enclosed in a delimited block that can be
parsed by the second pass rendering middleware.
"""
- def __init__(self, content, var_names):
- self.var_names = var_names
+ def __init__(self, content, var_names, components=None):
self.content = content
+ self.var_names = var_names
+ self.components = components or []
def __repr__(self):
return "<Phased Node: '%s'>" % smart_str(self.content[:25], 'ascii',
@@ -60,13 +62,12 @@ def render(self, context):
raise TemplateSyntaxError(
'"phased" tag got an unknown variable: %r' % var_name)
- storage = backup_csrf_token(context, storage)
-
# lastly return the pre phased template part
- return u'%(delimiter)s%(content)s%(pickled)s%(delimiter)s' % {
+ return u'%(delimiter)s%(content)s%(pickled_context)s%(pickled_components)s%(delimiter)s' % {
'content': self.content,
'delimiter': settings.SECRET_DELIMITER,
- 'pickled': pickle_context(storage),
+ 'pickled_context': pickle_context(backup_csrf_token(context, storage)),
+ 'pickled_components': pickle_components(self.components),
}
@@ -108,6 +109,6 @@ def do_phased(parser, token):
raise TemplateSyntaxError(u"'%r' tag requires the second argument to be 'with'." % tokens[0])
if len(tokens) == 2:
raise TemplateSyntaxError(u"'%r' tag requires at least one context variable name." % tokens[0])
- return PhasedNode(literal, tokens[2:])
+ return PhasedNode(literal, tokens[2:], components=find_components(parser))
register.tag('phased', do_phased)
View
25 phased/tests.py
@@ -32,8 +32,9 @@ def test_phased(self):
first_render = compile_string(self.test_template, None).render(context)
original_context = unpickle_context(first_render)
self.assertNotEqual(flatten_context(context), original_context)
- pickled_context = '{# stashed context: "gAJ9cQFVCmNzcmZfdG9rZW5xAlULTk9UUFJPVklERURxA3Mu" #}'
- self.assertEqual(first_render, '%(delimiter)s{%% if 1 %%}test{%% endif %%}%(pickled_context)s%(delimiter)sTEST' % dict(delimiter=settings.SECRET_DELIMITER, pickled_context=pickled_context))
+ pickled_context = '{# context "gAJ9cQFVCmNzcmZfdG9rZW5xAlULTk9UUFJPVklERURxA3Mu" endcontext #}'
+ pickled_components = '{# components "gAJdcQFVH3BoYXNlZC50ZW1wbGF0ZXRhZ3MucGhhc2VkX3RhZ3NxAmEu" endcomponents #}'
+ self.assertEqual(first_render, '%(delimiter)s{%% if 1 %%}test{%% endif %%}%(pickled_context)s%(pickled_components)s%(delimiter)sTEST' % dict(delimiter=settings.SECRET_DELIMITER, pickled_context=pickled_context, pickled_components=pickled_components))
def test_second_pass(self):
request = HttpRequest()
@@ -56,7 +57,8 @@ def tearDown(self):
def test_phased(self):
context = Context({'test_var': 'TEST'})
first_render = compile_string(self.test_template, None).render(context)
- self.assertEqual(first_render, 'fancydelimiter{%% if 1 %%}test{%% endif %%}%sfancydelimiterTEST' % pickle_context(backup_csrf_token(context)))
+ pickled_components = '{# components "gAJdcQFVH3BoYXNlZC50ZW1wbGF0ZXRhZ3MucGhhc2VkX3RhZ3NxAmEu" endcomponents #}'
+ self.assertEqual(first_render, 'fancydelimiter{%% if 1 %%}test{%% endif %%}%s%sfancydelimiterTEST' % (pickle_context(backup_csrf_token(context)), pickled_components))
def test_second_pass(self):
request = HttpRequest()
@@ -83,7 +85,8 @@ class NestedTwoPhaseTestCase(TwoPhaseTestCase):
def test_phased(self):
context = Context({'test_var': 'TEST'})
first_render = compile_string(self.test_template, None).render(context)
- self.assertEqual(first_render, '%(delimiter)s{%% load phased_tags %%}{%% phased %%}{%% if 1 %%}first{%% endif %%}{%% endphased %%}{%% if 1 %%}second{%% endif %%}%(pickled_context)s%(delimiter)sTEST' % dict(delimiter=settings.SECRET_DELIMITER, pickled_context=pickle_context(backup_csrf_token(context))))
+ pickled_components = '{# components "gAJdcQFVH3BoYXNlZC50ZW1wbGF0ZXRhZ3MucGhhc2VkX3RhZ3NxAmEu" endcomponents #}'
+ self.assertEqual(first_render, '%(delimiter)s{%% load phased_tags %%}{%% phased %%}{%% if 1 %%}first{%% endif %%}{%% endphased %%}{%% if 1 %%}second{%% endif %%}%(pickled_context)s%(pickled_components)s%(delimiter)sTEST' % dict(delimiter=settings.SECRET_DELIMITER, pickled_context=pickle_context(backup_csrf_token(context)), pickled_components=pickled_components))
def test_second_pass(self):
request = HttpRequest()
@@ -117,9 +120,10 @@ def setUp(self):
def test_phased(self):
context = Context({'test_var': 'TEST'})
- pickled_context = '{# stashed context: "gAJ9cQEoVQpjc3JmX3Rva2VucQJVC05PVFBST1ZJREVEcQNVCHRlc3RfdmFycQRVBFRFU1RxBXUu" #}'
+ pickled_context = '{# context "gAJ9cQEoVQpjc3JmX3Rva2VucQJVC05PVFBST1ZJREVEcQNVCHRlc3RfdmFycQRVBFRFU1RxBXUu" endcontext #}'
+ pickled_components = '{# components "gAJdcQFVH3BoYXNlZC50ZW1wbGF0ZXRhZ3MucGhhc2VkX3RhZ3NxAmEu" endcomponents #}'
first_render = compile_string(self.test_template, None).render(context)
- self.assertEqual(first_render, '%(delimiter)s{%% if 1 %%}test{%% endif %%}{%% if test_condition %%}stashed{%% endif %%}%(pickled_context)s%(delimiter)sTEST%(delimiter)s{%% if 1 %%}test2{%% endif %%}{%% if test_condition2 %%}stashed{%% endif %%}%(pickled_context)s%(delimiter)s' % dict(delimiter=settings.SECRET_DELIMITER, pickled_context=pickled_context))
+ self.assertEqual(first_render, '%(delimiter)s{%% if 1 %%}test{%% endif %%}{%% if test_condition %%}stashed{%% endif %%}%(pickled_context)s%(pickled_components)s%(delimiter)sTEST%(delimiter)s{%% if 1 %%}test2{%% endif %%}{%% if test_condition2 %%}stashed{%% endif %%}%(pickled_context)s{# components "gAJdcQFVH3BoYXNlZC50ZW1wbGF0ZXRhZ3MucGhhc2VkX3RhZ3NxAmEu" endcomponents #}%(delimiter)s' % dict(delimiter=settings.SECRET_DELIMITER, pickled_context=pickled_context, pickled_components=pickled_components))
def test_second_pass(self):
request = HttpRequest()
@@ -153,8 +157,9 @@ def test_phased(self):
'test_condition': True,
})
first_render = compile_string(self.test_template, None).render(context)
- pickled_context = '{# stashed context: "gAJ9cQEoVQ50ZXN0X2NvbmRpdGlvbnECiFUKY3NyZl90b2tlbnEDVQtOT1RQUk9WSURFRHEEVQh0ZXN0X3ZhcnEFVQRURVNUcQZ1Lg==" #}'
- self.assertEqual(first_render, '%(delimiter)s{%% if 1 %%}test{%% endif %%}{%% if test_condition %%}stashed{%% endif %%}%(pickled_context)s%(delimiter)sTEST' % dict(delimiter=settings.SECRET_DELIMITER, pickled_context=pickled_context))
+ pickled_context = '{# context "gAJ9cQEoVQ50ZXN0X2NvbmRpdGlvbnECiFUKY3NyZl90b2tlbnEDVQtOT1RQUk9WSURFRHEEVQh0ZXN0X3ZhcnEFVQRURVNUcQZ1Lg==" endcontext #}'
+ pickled_components = '{# components "gAJdcQFVH3BoYXNlZC50ZW1wbGF0ZXRhZ3MucGhhc2VkX3RhZ3NxAmEu" endcomponents #}'
+ self.assertEqual(first_render, '%(delimiter)s{%% if 1 %%}test{%% endif %%}{%% if test_condition %%}stashed{%% endif %%}%(pickled_context)s%(pickled_components)s%(delimiter)sTEST' % dict(delimiter=settings.SECRET_DELIMITER, pickled_context=pickled_context, pickled_components=pickled_components))
def test_second_pass(self):
request = HttpRequest()
@@ -185,10 +190,10 @@ def test_flatten_nested(self):
def test_pickling(self):
self.assertRaises(TemplateSyntaxError, pickle_context, {})
- self.assertEqual(pickle_context(Context()), '{# stashed context: "gAJ9Lg==" #}')
+ self.assertEqual(pickle_context(Context()), '{# context "gAJ9Lg==" endcontext #}')
context = Context({'test_var': 'TEST'})
template = '<!-- better be careful %s yikes -->'
- self.assertEqual(pickle_context(context), '{# stashed context: "gAJ9cQFVCHRlc3RfdmFycQJVBFRFU1RxA3Mu" #}')
+ self.assertEqual(pickle_context(context), '{# context "gAJ9cQFVCHRlc3RfdmFycQJVBFRFU1RxA3Mu" endcontext #}')
self.assertEqual(pickle_context(context, template), '<!-- better be careful gAJ9cQFVCHRlc3RfdmFycQJVBFRFU1RxA3Mu yikes -->')
def test_unpickling(self):
View
67 phased/utils.py
@@ -1,23 +1,31 @@
-import re, base64
+import re, base64, itertools
from django.conf import settings as django_settings
-from django.template.context import BaseContext, RequestContext, Context
-from django.template import (Parser, Lexer, Token,
+from django.http import HttpRequest
+from django.template import (Parser, Lexer, Token, import_library,
TOKEN_TEXT, COMMENT_TAG_START, COMMENT_TAG_END, TemplateSyntaxError)
+from django.template.context import BaseContext, RequestContext, Context
from django.utils.cache import cc_delim_re
from django.utils.functional import Promise, LazyObject
-from django.http import HttpRequest
-from django.contrib.messages.storage.base import BaseStorage
from django.utils.encoding import smart_str
+from django.contrib.messages.storage.base import BaseStorage
+
try:
import cPickle as pickle
except ImportError:
import pickle
from phased import settings
-pickled_context_re = re.compile(r'.*%s stashed context: "(.*)" %s.*' % (COMMENT_TAG_START, COMMENT_TAG_END))
+pickled_context_re = re.compile(r'.*%s context "(.*)" endcontext %s.*' % (COMMENT_TAG_START, COMMENT_TAG_END))
+pickled_components_re = re.compile(r'.*%s components "(.*)" endcomponents %s.*' % (COMMENT_TAG_START, COMMENT_TAG_END))
+
forbidden_classes = (Promise, LazyObject, HttpRequest, BaseStorage)
+forbidden_components = (
+ 'django.template.defaultfilters',
+ 'django.template.defaulttags',
+ 'django.template.loader_tags'
+)
def second_pass_render(request, content):
"""
@@ -33,11 +41,17 @@ def second_pass_render(request, content):
tokens = Lexer(bit, None).tokenize()
else:
tokens.append(Token(TOKEN_TEXT, bit))
-
+ # restore the previos context including the CSRF token
context = RequestContext(request,
restore_csrf_token(request, unpickle_context(bit)))
- rendered = Parser(tokens).parse().render(context)
-
+ # restore the loaded components (tags and filters)
+ parser = Parser(tokens)
+ unpickled_components = unpickle_components(bit) or []
+ for component in unpickled_components:
+ lib = import_library(component)
+ parser.add_library(lib)
+ # render the piece with the restored context
+ rendered = parser.parse().render(context)
if settings.SECRET_DELIMITER in rendered:
rendered = second_pass_render(request, rendered)
result.append(rendered)
@@ -70,6 +84,17 @@ def backup_csrf_token(context, storage=None):
storage['csrf_token'] = smart_str(context.get('csrf_token', 'NOTPROVIDED'))
return storage
+def find_components(parser):
+ """
+ Return a list of template tags library dotted paths to stash it away for
+ later automagic loading during the second rendering.
+ """
+ modules = set()
+ for func in itertools.chain(parser.tags.itervalues(), parser.filters.itervalues()):
+ if func.__module__ not in forbidden_components:
+ modules.add(func.__module__)
+ return list(modules)
+
def drop_vary_headers(response, headers_to_drop):
"""
Remove an item from the "Vary" header of an ``HttpResponse`` object.
@@ -137,7 +162,27 @@ def pickle_context(context, template=None):
"""
if not isinstance(context, BaseContext):
raise TemplateSyntaxError('Phased context is not a Context instance')
- pickled_context = pickle.dumps(flatten_context(context), protocol=pickle.HIGHEST_PROTOCOL)
if template is None:
- template = '{# stashed context: "%s" #}'
+ template = '{# context "%s" endcontext #}'
+ pickled_context = pickle.dumps(flatten_context(context), protocol=pickle.HIGHEST_PROTOCOL)
return template % base64.standard_b64encode(pickled_context)
+
+def pickle_components(components, template=None):
+ """
+ Pickle the list of components and base64 them.
+ """
+ if template is None:
+ template = '{# components "%s" endcomponents #}'
+ pickled_components = pickle.dumps(components, protocol=pickle.HIGHEST_PROTOCOL)
+ return template % base64.standard_b64encode(pickled_components)
+
+def unpickle_components(content, pattern=None):
+ """
+ Unpickle the components from the given content string or return None.
+ """
+ if pattern is None:
+ pattern = pickled_components_re
+ match = pattern.search(content)
+ if match:
+ return pickle.loads(base64.standard_b64decode(match.group(1)))
+ return None

0 comments on commit e055268

Please sign in to comment.
Something went wrong with that request. Please try again.