Jonas Obrist Python enthusiast

Blog

Why Sekizai

May 26, 2013

There's a lot of confusion about a Django app I wrote, django sekizai. I'd like to explain why I wrote it and what it does, as people seem to be a little confused about both.

Sekizai originated over the course of three days from a conversation I had with Angelo, a Frontend Engineer I work with at Divio. He had to write some Django templates for Plugins for django CMS. The conversation went a little like this:

Day 1 (morning):
Angelo: Jonas, I'm writing a Plugin that depends on Javascript libraries, but I don't want to load them in the middle of the template, and the plugin might be on a page multiple times and I don't want to load the library more than once. Can you add something so the library is loaded at the end of the the HTML document? Me: That's impossible.
Day 1 (evening):
Me: Hey Angelo, I wrote you the template tag you needed, just do {% add_to_end %}content{% end_add_to_end %}. Angelo: GREAT!
Day 2 (morning):
Angelo: About that thing you wrote me yesterday... I'm now writing a plugin that needs external CSS styles that I want to appear in the head of the HTML document, could you do something like that? Me: Sorry, that's not possible.
Day 2 (evening):
Me: Hey, so about the thing you wanted this morning. Just do {% add_to_head %}content{% end_add_to_head %} and it should work. Angelo: AWESOME! Me: But there's a catch, you can't use it in the same project as the {% add_to_end %} I did for you yesterday. Angelo: Don't worry, this is another project anyway...
Day 3 (morning):
Angelo: Well.......... Looks like I need {% add_to_head %} and {% add_to_end %} in the same project after all... Could you make it so that works? Me: Impossible! That will never work!
Day 3 (evening):
Me: I just wrote this thing called django-sekizai that does what you want. Go crazy! Angelo: YAY!

So why was this needed in the first place? Let's have a look how django CMS renders a page. The django CMS view looks up the page, gets its template and renders it. Within that template, there can be {% placeholder 'name' %} tags, which look up the placeholders on the current page. Then the tag renders all plugins inside that placeholder and returns the rendered string. The only thing shared between the plugins is a common base context. This is what sekizai exploits to work.

Sekizai has two main APIs, both template tags:{% render_block 'namespace' %} and {% addtoblock 'namespace' %}...{% endaddtoblock %}. The first is where the content of that namespace will end up, the latter adds stuff to a namespace.

But we all know that Django parses and renders templates top-to-bottom. So how can sekizai output content in the {% render_block %} that is before the {% addtoblock %} call?

It cheats. Now we come to the juicy bit. The way sekizai works.

Let me introduce an example template:

{% load sekizai_tags %}

{% render_block "css" %}

{% for css, js in some_list %}
    {% addtoblock "css" %}{{ css }}{% endaddtoblock %}
    {% addtoblock "js" %}{{ js }}{% endaddtoblock %}
{% endfor %}

{% render_block "js" %}

Now if we render this (with SekizaiContext or using the context processor and RequestContext), what actually happens is:

  1. SekizaiContext or RequestContext add an object to the base context which will keep track of data, this is basically a glorified dictionary.
  2. Django hits the load tag, loads the sekizai library.
  3. Django hits the render_block tag, sekizai takes over.
  4. The render_block tag now continues to parse the rest of the template and stores the resulting list of nodes. [1]
  5. Once parsed, it renders the the nodelist.
  6. Inside the for loop, addtoblock adds data to the sekizai object added in step 1. They don't output anything.
  7. We hit a second render_block here. It does the same the first render_block (step 3) did.
  8. The first render_block now has the rendered content that follows after it.
  9. The first render_block gets the data from the sekizai object in step 1 for its namespace
  10. The first render_block outputs its data, followed by the content following after it.
  11. Done.
[1]This is why it can't be within a block-like tag. Any Django template tag that has an end tag will parse until that end tag or raise an exception if it can't find it. Since render_block parses until the end of the template, it will have consumed the end tag, thus breaking the block tag.

In short, render_block parses the template until the end, stores the following nodes and renders them. Then it looks at an object in the context which holds the data for its namespace. Any node that was before the render_block tag will already have added that content to the context and any node that comes after it will have been rendered by the render_block tag, thus also have added it's content to the context. It then outputs the content for its namespace, followed by the output generated by all the nodes that follow.

I hope by now it's clear that sekizai is not a replacement for things such as django-compressor or django-pipeline, as it doesn't actually deal with assets, but rather content. Usually this content happens to be assets, which is where the confusion comes from.

But fear not, you can use sekizai together with compressor and similar projects! render_block takes one or three arguments. The first is the namespace it should render, the optional two following arguments are postprocess 'path.to.callable'. That path to a callable is a dotted import path to a Python callable that post processes the content in that namespace. It is documented but it looks like I need to move that bit to a more prominent location so people find it easier.

Basically a post processor is any Python callable that takes 3 arguments, context which is the template context, data which is the rendered content inside this namespace and namespace which is the name of the namespace as string. It can then do whatever it wants with this and should return a string.

Compressor even has built-in support for this, see their documentation!

I hope this clears some misconceptions, if not, ask in the comments!

comments powered by Disqus