Permalink
Browse files

all calls are proxied by context.call now so that we can inject envir…

…onment and context as first arguments. This slows calls down a bit but is a lot more user friendly. Added first draft of FAQ

--HG--
branch : trunk
  • Loading branch information...
1 parent 9f258ff commit fd31049f62b962181e0413b59bed9529b9df7b2b @mitsuhiko mitsuhiko committed May 24, 2008
Showing with 180 additions and 48 deletions.
  1. +123 −0 docs/faq.rst
  2. +1 −0 docs/index.rst
  3. +15 −0 docs/templates.rst
  4. +13 −24 jinja2/compiler.py
  5. +4 −6 jinja2/nodes.py
  6. +1 −0 jinja2/parser.py
  7. +11 −8 jinja2/runtime.py
  8. +2 −2 jinja2/sandbox.py
  9. +3 −3 jinja2/utils.py
  10. +7 −5 tests/test_syntax.py
View
@@ -0,0 +1,123 @@
+Frequently Asked Questions
+==========================
+
+This page answers some of the often asked questions about Jinja.
+
+.. highlight:: html+jinja
+
+
+Why is it called Jinja?
+-----------------------
+
+The name Jinja was chosen because it's the name of a Japanese temple and
+temple and template share a similar pronunciation. It is not named after
+the capital city of Uganda.
+
+How fast is it?
+---------------
+
+We really hate benchmarks especially since they don't reflect much. The
+performance of a template depends on many factors and you would have to
+benchmark different engines in different situations. The benchmarks from the
+testsuite show that Jinja2 has a similar performance to `Mako`_ and is more
+than 20 times faster than Django's template engine or Genshi. These numbers
+should be taken with tons of salt!
+
+.. _Mako: http://www.makotemplates.org/
+
+How Compatible is Jinja2 with Django?
+-------------------------------------
+
+The default syntax of Jinja2 matches Django syntax in many ways. However
+this similarity doesn't mean that you can use a Django template unmodified
+in Jinja2. For example filter arguments use a function call syntax rather
+than a colon to separate filter name and arguments. Additionally the
+extension interface in Jinja is fundamentally different from the Django one
+which means that your custom tags won't work any longer.
+
+Generally speaking you will use much less custom extensions as the Jinja
+template system allows you to use a certain subset of Python expressions
+which can replace most Django extensions. For example instead of using
+something like this::
+
+ {% load comments %}
+ {% get_latest_comments 10 as latest_comments %}
+ {% for comment in latest_comments %}
+ ...
+ {% endfor %}
+
+You will most likely provide an object with attributes to retrieve
+comments from the database::
+
+ {% for comment in models.comments.latest(10) %}
+ ...
+ {% endfor %}
+
+Or directly provide the model for quick testing::
+
+ {% for comment in Comment.objects.order_by('-pub_date')[:10] %}
+ ...
+ {% endfor %}
+
+Please keep in mind that even though you may put such things into templates
+it still isn't a good idea. Queries should go into the view code and now
+the template!
+
+Isn't it a terrible idea to put Logic into Templates?
+-----------------------------------------------------
+
+Without a doubt you should try to remove as much logic from templates as
+possible. But templates without any logic mean that you have to do all
+the processing in the code which is boring and stupid. A template engine
+that does that is shipped with Python and called `string.Template`. Comes
+without loops and if conditions and is by far the fastest template engine
+you can get for Python.
+
+So some amount of logic is required in templates to keep everyone happy.
+And Jinja leaves it pretty much to you how much logic you want to put into
+templates. There are some restrictions in what you can do and what not.
+
+Jinja2 neither allows you to put arbitrary Python code into templates nor
+does it allow all Python expressions. The operators are limited to the
+most common ones and more advanced expressions such as list comprehensions
+and generator expressions are not supported. This keeps the template engine
+easier to maintain and templates more readable.
+
+Why is Autoescaping not the Default?
+------------------------------------
+
+There are multiple reasons why automatic escaping is not the default mode
+and also not the recommended one. While automatic escaping of variables
+means that you will less likely have an XSS problem it also causes a huge
+amount of extra processing in the template engine which can cause serious
+performance problems. As Python doesn't provide a way to mark strings as
+unsafe Jinja has to hack around that limitation by providing a custom
+string class (the :class:`Markup` string) that safely interacts with safe
+and unsafe strings.
+
+With explicit escaping however the template engine doesn't have to perform
+any safety checks on variables. Also a human knows not to escape integers
+or strings that may never contain characters one has to escape or already
+HTML markup. For example when iterating over a list over a table of
+integers and floats for a table of statistics the template designer can
+omit the escaping because he knows that integers or floats don't contain
+any unsafe parameters.
+
+Additionally Jinja2 is a general purpose template engine and not only used
+for HTML/XML generation. For example you may generate LaTeX, emails,
+CSS, JavaScript, or configuration files.
+
+Why is the Context immutable?
+-----------------------------
+
+When writing a :func:`contextfunction` or something similar you may have
+noticed that the context tries to stop you from modifying it. If you have
+managed to modify the context by using an internal context API you may
+have noticed that changes in the context don't seem to be visible in the
+template. The reason for this is that Jinja uses the context only as
+primary data source for template variables for performance reasons.
+
+If you want to modify the context write a function that returns a variable
+instead that one can assign to a variable by using set::
+
+ {% set comments = get_latest_comments() %}
View
@@ -17,6 +17,7 @@ fast and secure.
switching
tricks
+ faq
changelog
If you can't find the information you're looking for, have a look at the
View
@@ -326,6 +326,21 @@ This gives back the results of the parent block::
{% endblock %}
+Named Block End-Tags
+~~~~~~~~~~~~~~~~~~~~
+
+Jinja2 allows you to put the name of the block after the end tag for better
+readability::
+
+ {% block sidebar %}
+ {% block inner_sidebar %}
+ ...
+ {% endblock inner_sidebar %}
+ {% endblock sidebar %}
+
+However the name after the `endblock` word must match the block name.
+
+
HTML Escaping
-------------
View
@@ -438,21 +438,13 @@ def newline(self, node=None, extra=0):
self._write_debug_info = node.lineno
self._last_line = node.lineno
- def signature(self, node, frame, have_comma=True, extra_kwargs=None):
+ def signature(self, node, frame, extra_kwargs=None):
"""Writes a function call to the stream for the current node.
- Per default it will write a leading comma but this can be
- disabled by setting have_comma to False. The extra keyword
+ A leading comma is added automatically. The extra keyword
arguments may not include python keywords otherwise a syntax
error could occour. The extra keyword arguments should be given
as python dict.
"""
- have_comma = have_comma and [True] or []
- def touch_comma():
- if have_comma:
- self.write(', ')
- else:
- have_comma.append(True)
-
# if any of the given keyword arguments is a python keyword
# we have to make sure that no invalid call is created.
kwarg_workaround = False
@@ -462,28 +454,25 @@ def touch_comma():
break
for arg in node.args:
- touch_comma()
+ self.write(', ')
self.visit(arg, frame)
if not kwarg_workaround:
for kwarg in node.kwargs:
- touch_comma()
+ self.write(', ')
self.visit(kwarg, frame)
if extra_kwargs is not None:
for key, value in extra_kwargs.iteritems():
- touch_comma()
- self.write('%s=%s' % (key, value))
+ self.write(', %s=%s' % (key, value))
if node.dyn_args:
- touch_comma()
- self.write('*')
+ self.write(', *')
self.visit(node.dyn_args, frame)
if kwarg_workaround:
- touch_comma()
if node.dyn_kwargs is not None:
- self.write('**dict({')
+ self.write(', **dict({')
else:
- self.write('**{')
+ self.write(', **{')
for kwarg in node.kwargs:
self.write('%r: ' % kwarg.key)
self.visit(kwarg.value, frame)
@@ -499,8 +488,7 @@ def touch_comma():
self.write('}')
elif node.dyn_kwargs is not None:
- touch_comma()
- self.write('**')
+ self.write(', **')
self.visit(node.dyn_kwargs, frame)
def pull_locals(self, frame):
@@ -1353,11 +1341,12 @@ def visit_CondExpr(self, node, frame):
def visit_Call(self, node, frame, forward_caller=False):
if self.environment.sandboxed:
- self.write('environment.call(')
+ self.write('environment.call(context, ')
+ else:
+ self.write('context.call(')
self.visit(node.node, frame)
- self.write(self.environment.sandboxed and ', ' or '(')
extra_kwargs = forward_caller and {'caller': 'caller'} or None
- self.signature(node, frame, False, extra_kwargs)
+ self.signature(node, frame, extra_kwargs)
self.write(')')
def visit_Keyword(self, node, frame):
View
@@ -14,7 +14,6 @@
"""
import operator
from copy import copy
-from types import FunctionType
from itertools import chain, izip
from collections import deque
from jinja2.utils import Markup
@@ -550,11 +549,10 @@ def as_const(self):
# don't evaluate context functions
args = [x.as_const() for x in self.args]
- if type(obj) is FunctionType:
- if getattr(obj, 'contextfunction', False):
- raise Impossible()
- elif obj.environmentfunction:
- args.insert(0, self.environment)
+ if getattr(obj, 'contextfunction', False):
+ raise Impossible()
+ elif getattr(obj, 'environmentfunction', False):
+ args.insert(0, self.environment)
kwargs = dict(x.as_const() for x in self.kwargs)
if self.dyn_args is not None:
View
@@ -150,6 +150,7 @@ def parse_block(self):
node = nodes.Block(lineno=self.stream.next().lineno)
node.name = self.stream.expect('name').value
node.body = self.parse_statements(('name:endblock',), drop_needle=True)
+ self.stream.skip_if('name:' + node.name)
return node
def parse_extends(self):
View
@@ -66,14 +66,6 @@ def __init__(self, environment, parent, name, blocks):
self.exported_vars = set()
self.name = name
- # bind functions to the context of environment if required
- for name, obj in parent.iteritems():
- if type(obj) is FunctionType:
- if getattr(obj, 'contextfunction', 0):
- vars[name] = partial(obj, self)
- elif getattr(obj, 'environmentfunction', 0):
- vars[name] = partial(obj, environment)
-
# create the initial mapping of blocks. Whenever template inheritance
# takes place the runtime will update this mapping with the new blocks
# from the template.
@@ -122,6 +114,17 @@ def get_all(self):
"""
return dict(self.parent, **self.vars)
+ def call(__self, __obj, *args, **kwargs):
+ """Called by the template code to inject the current context
+ or environment as first arguments. Then forwards the call to
+ the object with the arguments and keyword arguments.
+ """
+ if getattr(__obj, 'contextfunction', 0):
+ args = (__self,) + args
+ elif getattr(__obj, 'environmentfunction', 0):
+ args = (__self.environment,) + args
+ return __obj(*args, **kwargs)
+
def _all(meth):
proxy = lambda self: getattr(self.get_all(), meth)()
proxy.__doc__ = getattr(dict, meth).__doc__
View
@@ -190,13 +190,13 @@ def subscribe(self, obj, argument):
), name=argument, exc=SecurityError)
return self.undefined(obj=obj, name=argument)
- def call(__self, __obj, *args, **kwargs):
+ def call(__self, __context, __obj, *args, **kwargs):
"""Call an object from sandboxed code."""
# the double prefixes are to avoid double keyword argument
# errors when proxying the call.
if not __self.is_safe_callable(__obj):
raise SecurityError('%r is not safely callable' % (__obj,))
- return __obj(*args, **kwargs)
+ return __context.call(__obj, *args, **kwargs)
class ImmutableSandboxedEnvironment(SandboxedEnvironment):
View
@@ -64,8 +64,8 @@ def concat(gen):
def contextfunction(f):
"""This decorator can be used to mark a callable as context callable. A
- context callable is passed the active context as first argument if it
- was directly stored in the context.
+ context callable is passed the active context as first argument when
+ called from the template.
"""
f.contextfunction = True
return f
@@ -74,7 +74,7 @@ def contextfunction(f):
def environmentfunction(f):
"""This decorator can be used to mark a callable as environment callable.
A environment callable is passed the current environment as first argument
- if it was directly stored in the context.
+ when called from the template.
"""
f.environmentfunction = True
return f
View
@@ -6,6 +6,7 @@
:copyright: 2007 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
+from py.test import raises
from jinja2 import Environment, DictLoader
from jinja2.exceptions import TemplateSyntaxError
@@ -143,11 +144,7 @@ def test_function_calls(env):
]
for should_fail, sig in tests:
if should_fail:
- try:
- print env.from_string('{{ foo(%s) }}' % sig)
- except TemplateSyntaxError:
- continue
- assert False, 'expected syntax error'
+ raises(TemplateSyntaxError, env.from_string, '{{ foo(%s) }}' % sig)
else:
env.from_string('foo(%s)' % sig)
@@ -161,3 +158,8 @@ def test_tuple_expr(env):
def test_trailing_comma(env):
tmpl = env.from_string(TRAILINGCOMMA)
assert tmpl.render().lower() == '(1, 2)|[1, 2]|{1: 2}'
+
+
+def test_block_end_name(env):
+ env.from_string('{% block foo %}...{% endblock foo %}')
+ raises(TemplateSyntaxError, env.from_string, '{% block x %}{% endblock y %}')

0 comments on commit fd31049

Please sign in to comment.