Hi everybody,

After my quite controversal blog post about Jinja and Django I summed
up some suggestions for the Django template engine that don't require
a complete reimplementation / breaking backwards compatibility :)

First of all the thread in-safeties I discovered [and the explanation
why nobody noticed so far].  First of all the explanation, because
that's probably more interesting: Per default templates are parsed and
evaluated each request so you will never notice that.  However I've
seen enough applications by now that keep the template objects in
memory where these problems become visible.  Why would anyone want to
not parse the templates each request?  Because it's a lot faster.  How
much?  ~100 req/sec versus 60 req/sec for the file browser page on
bitbucket, and that's the overall request time including all of the
request handling, http header parsing and of course database.

Where are threading bugs?  Everywhere, where a tag render method
somehow modifies the node object.  Where does this happen?  From what
I've seen so far it's done in "block", "ifchanged", "cycle" and
"extends".  Now, of course the attribute assignments happen for a
reason: they are necessary, at least for cycle and ifchanged.  Block
and extends look like accidents, they could be solved a lot cleaner
(block would put a reference to a hypothetical SuperBlock into the
context, rather than a reference to itself) and extends doesn't have
to store the evaluated value on the node at all, a local variable is
just fine.  Ifchanged and cycle however have to somewhere store the
information, and I recommend using the context object for that.  The
context could get `get_state_var`, `set_state_var` functions that can
be used to store node related information from one render call to the
next::

    def get_state_var(self, node, name, default=None):
        storage = self._state_vars.get(id(node), None)
        if storage is not None and name in storage:
            return storage[name]
        return default

    def set_state_var(self, node, name, value):
        self._state_vars.setdefault(id(node), {})[name] = value

The cycle node could then look like this::

    class CycleNode(Node):
        def __init__(self, cyclevars, variable_name=None):
            self.cycle_vars = map(Variable, cyclevars)
            self.variable_name = variable_name

        def render(self, context):
            pos = context.get_state_var(self, 'pos', 0)
            value = self.cycle_iter[pos].resolve(context)
            context.set_state_var(self, 'pos', (pos + 1) %
len(self.cycle_vars))
            if self.variable_name:
                context[self.variable_name] = value
            return value

This used in all core tags makes the `Template` object thread safe,
but not the `Context` which is perfectly okay because it doesn't
happen (or should not happen) that a context object is shared between
threads.  At least I don't see a situation where this is useful.

What's harder to fix is how the i18n integration translates filter
arguments or gettext constants (those _("foo") thingies).  Currently
that happens very often at node/object creation rather than node/
object evaluation.  A good example for that problem is
FilterExpression.__init__.  The translation would have to be moved to
the resolve method in that case.  When would a language change between
template compilation and rendering?  If the language changes each
request which is a very common case on multilingual websites.

Changing the parser to a more advanced system or making the template
engine more consistent is not so important for the time being, but I
want to raise a few situations I encountered where the behaviour is
confusing:

  - cycle arguments are delimited by spaces and each item can be an
    expression (however if there is a comma involved somewhere, it
    seems like the tag is interpreted as comma separated list of
strings
    which makes the 'cycle "foo", "bar", "baz"' yield unexpected
results.

  - On the other hand arguments for the url tag are comma delimited,
    but whitespace after comma is not allowed.

  - The group-by part of the regroup tag is an expression, but
everything
    else but a variable name returns unexpected results.  Furthermore
    does this tag only work on dicts.  By the group-by part I'm
refering
    to the expression after the "by" keyword:

      {% regroupy foo by bar as blah %}

    bar is here treated as constant string, even though it's
represented
    as variable in the regroup node and in the syntax.

  - Likewise ssi excepts one argument that looks like a variable, but
is
    treated as constant string.

Tags to be excluded from the variable-as-string rule should be block
and load because they represent neither variables nor strings.  The
argument for block works like a label and the argument for load is a
library.

Regards,
Armin
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to