Alternativa al sistema de templates de Django, parte 1: Jinja2

Django es un gran framework web pero tiene un sistema de templates que a un programador puede resultarle un tanto limitado:

  • No es posible asignar variables ni cambiar sus valores.
  • No hay distinción entre métodos y propiedades.
  • No es posible pasar parámetros a los métodos.
  • No existe la sentencia elif ! :P

Hay varias alternativas entre las que elegir: Jinja2, Mako, Genshi, Cheetah. De todas, Jinja2 es el que tiene la sintáxis más compatible con Django, pero tiene otra filosofía y por lo tanto permite hacer más cosas. Empecemos entonces por la instalación.

Instalación de Jinja2

Jinja puede instalarse medianteeasy_install:

sudo easy_install jinja2

Y medianteapten debian y derivados:

sudo apt-get install python-jinja2

Integración con Django

El diseño de bajo acoplamiento de Django permite que usar otro sistema de templates sea sencillo. Para Jinja basta con definir una variable de ambiente y escribir los métodos alternativos pararender_to_stringyrender_to_response. A continuación muestro la forma más simple que encontré. Hay otras más sofisticadas por ahí (esta y esta) pero básicamente hacen lo mismo:

# jinja_helper.py
from django.conf import settings
from django.http import HttpResponse
from jinja2 import Environment, FileSystemLoader

jinja_env = Environment(
    loader=FileSystemLoader(getattr(settings, 'TEMPLATE_DIRS')))

def render_to_string(template_path, context=None, **kwargs):
    template = jinja_env.get_template(template_path)
    context = dict(context or {})
    return template.render(**context)

def render_to_response(template_path, context=None, **kwargs):
    return HttpResponse(render_to_string(template_path, context, **kwargs),
                        mimetype=None)

La vista entonces no difiere mucho de una vista tradicional:

# views.py
from jinja_helper import render_to_response

def index(request):
    title = "hello world."
    return render_to_response('index.html', locals())
<!-- index.html -->
<h1>{{ title }}</h1>

Escribir un libro filtro

Un filtro es una función de python que se agrega a la listafilters:

# filter.py
from jinja_helper import jinja_env

def toupper(s):
    return s.upper()

jinja_env.filters['toupper'] = toupper

Cuando el filtro recibe un sólo argumento la forma de uso es similar a Django:

<!-- index.html -->
<h1>{{ title|toupper }}</h1>

Para pasar más argumentos se usa la formaarg1|func(arg2, ...).

Para la asignación a jinja_env.filter pordemos usar un decorador:

# filter.py
from jinja_helper import register_filter

@register_filter()
def toupper(s):
    return s.upper()
# jinja_helper.py
def register_filter(name=None):
    def dec(func):
        if name:
            func.__name__ = name
        jinja_env.filters[func.__name__] = func
        return func
    return dec

Tags? no, funciones

Para emular un tag de inclusión de django hay que definir una función que retorne un render_to_string y agregarla a la listaglobals:

# blocks.py
from jinja_helper import jinja_env, render_to_string

def menu():
    items = ('about', 'contact')
    return render_to_string('menu.html', dict(items=items))

jinja_env.globals['menu'] = menu

El template podría ser algo así:

<!-- menu.html -->
<ul>
{% for item in items %}
    <li>{{ item }}</li>
{% endfor %}
</ul>
<!-- index.html -->
{{ menu() }}

De nuevo, podríamos agregar un poco de azúcar a la sintáxis:

# blocks.py
from jinja_helper import register_block

@register_block('menu.html')
def menu():
    items = ('about', 'contact')
    return dict(items=items)
# jinja_helper.py
def register_block(template_path, name=None):
    def dec(func):
        def func2(*args, **kwargs):
            return render_to_string(template_path, func(*args, **kwargs))
        func2.__name__ = name if name else func.__name__
        jinja_env.globals[func2.__name__] = func2
        return func2
    return dec

Descarga

El ejemplo completo puede descargarse desde aquí: mysite-jinja2.tar

Tags: , ,

Leave a Reply