Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Thread Safety with Template Tags

After reading this document about thread safety, I am left feeling that there is something missing in the documentation, or my reading of it, or my reasoning.

Let's give a simple example:

class HelloWorldNode(template.Node):
    def render(self, context):
        return "O HAI LOL"

@register.tag(name="hello_world")
def hello_world(parser, tokens):
    """
    Greets the world with wide-eyed awe.
    """
    return HelloWorldNode()

I understood this code to construct a new instance of the HelloWorldNode class whenever the hello_world tag is used. Other examples involve passing arguments to the constructor, like this:

class HelloWorldNode(template.Node):
    def __init__(self, message):
        self.message = message

    def render(self, context):
        return "O HAI LOL " + message

@register.tag(name="hello_world")
def hello_world(parser, tokens):
    """
    Greets the world with wide-eyed awe.
    """

    message = tokens.split_contents()[1]

    return HelloWorldNode(message)

Thus, when hello_world is executed, a new instance of HelloWorldNode is created, and the instance dictionary has an attribute message. This instance surely must be used only for the rendering of only the given instance of the tag, as using it for other renderings would mean that the data bound to it would be incorrect. If this were not the case, the arguments would get mixed up between different uses of the tag.

Looking at other examples from the docs, here's a simplified example from here:

def do_current_time(parser, token):
    tag_name, format_string = token.split_contents()
    return CurrentTimeNode(format_string[1:-1])

As this takes data from the tokens passed to the function, the only way that CurrentTimeNode can work is that a new one is instantiated each time do_current_time is invoked.

Back to the documentation page, where the dissonance sets in. This is 'bad'.

class CycleNode(Node):
    def __init__(self, cyclevars):
        self.cycle_iter = itertools.cycle(cyclevars)
    def render(self, context):
        return self.cycle_iter.next()

The doc says that two pages using the same tag might then experience race conditions if they both use the same node. I don't understand how two templates' rendering could end up sharing the same instance if they both independently instantiate their own.

The way to solve this, says the docs is like this:

class CycleNode(Node):
    def __init__(self, cyclevars):
        self.cyclevars = cyclevars
    def render(self, context):
        if self not in context.render_context:
            context.render_context[self] = itertools.cycle(self.cyclevars)
        cycle_iter = context.render_context[self]
        return cycle_iter.next()

This appears to index context.render_context with self. The implication of that must be that self is used to identify the instance in one of two ways:

  1. self references one specific instance of the class in the whole system
  2. self references that class only, and in order to reference the instance a render context is required

If 1 is true, why not just associate data with self?

If 2 is true, and the render context is "associated with the context of the template that is currently being rendered", how is it possible to distinguish between two instance of the template tag on the same page?

Is the Node instantiated individually each time the tag is invoked? If so, why the concurrency problems? If not, why not?

like image 532
Joe Avatar asked Sep 20 '11 10:09

Joe


People also ask

What is the use of template tag in Django?

The template tags are a way of telling Django that here comes something else than plain HTML. The template tags allows us to to do some programming on the server before sending HTML to the client.

What {% include %} does?

{% include %} Processes a partial template. Any variables in the parent template will be available in the partial template. Variables set from the partial template using the set or assign tags will be available in the parent template.

What does the built in Django template filter safe do?

This flag tells Django that if a “safe” string is passed into your filter, the result will still be “safe” and if a non-safe string is passed in, Django will automatically escape it, if necessary. You can think of this as meaning “this filter is safe – it doesn't introduce any possibility of unsafe HTML.”

How do I register a custom template tag in Django?

Create a custom template tagUnder the application directory, create the templatetags package (it should contain the __init__.py file). For example, Django/DjangoApp/templatetags. In the templatetags package, create a . py file, for example my_custom_tags, and add some code to it to declare a custom tag.


1 Answers

Got it with closer reading of this.

The template is compiled when it is loaded. Any arguments passed into the tag function are 'static'. They are either literal strings, or they are strings which are used as identifiers to look up bound variables in the render context.

Therefore the Node object is instantiated for each tag, and hangs around ready for use whenever the template is used (and naturally the template may be used in any number of threads).

Thus the self in my question is the identity of the specific Node within the template. Combined with the render context, this gives a unique identity on which to hang instance variables.

like image 155
Joe Avatar answered Nov 10 '22 01:11

Joe