Login

Jinja2 integration + application specific functions/filters/tests

Author:
hasenj
Posted:
October 5, 2008
Language:
Python
Version:
1.0
Score:
2 (after 2 ratings)

Jinja2 is an alternative template system that can be plugged into django. It offers greator flexibility in presentation logic; if you find the django template system too restrictive, you should have a look at Jinja2 (The syntax is very similar).

In Jinja, you don't need costum tags (most of the time), because you can call functions and pass them parameters too! The only problem is that you cannot "load" functions from the template, this "loading" must be done by the code that renders the template. Same goes for filters and tests.

If you need only two or three functions/filters, no problem, you can manually add them to the Environment object; but this method doesn't really scale with real-life webapps.

This module/snippet offers a solution by allowing the user to define application-specific functions and filters and load them into the jinja2 environment automatically.

Here's how to use this:

  1. Install Jinja2 (for a quick & dirty installation, you can just put the jinja2 folder in a folder that's in PYTHONPATH)
  2. Save this python module somewhere and call it something meaningful (I have it as jinja.py in my project directory)
  3. Whenever you need to respond (an HttpResponse) with a template that's rendered by jinja, import this module and call return jrespond( template_name, context )
  4. Any filters or functions you define in any of your applications will be readily available inside the template (see the documentation in code)
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
"""
    Application specific functions/filters/tests in Jinja2
    (note: this is not complete, but it works for me! I hope it can be useful for you)
        
    The Integration code was originally based on 
    http://lethain.com/entry/2008/jul/22/replacing-django-s-template-language-with-jinja2/  
    But I changed it so much that nothing much is left from the original.
    

    This module provides jinja2 integration with django that features 
    automatic loading of application-specific functions/filters/tests to the
    environment of Jinja
    (plus enabling auto escape)

 
    To define global functions:
      put a jtemp.py module in your application directory, and it will be imported 
    into the globals dictionary of the environment under the namespace of your 
    application.
    so if your app is called "news" and you define a function "top_stories" in jtemp.py
    then from the template, you can access it as "news.top_stories()", for example:
    {% set top_stories = news.top_stories() %}
 
    To define filters:
      put a jfilters.py module in your application directory, and any function in it
    that  starts with "filter_" will be added to the filters dictionary of the
    environment without the filter_ prefix (so filter_url will be called "url")
 
    To define tests:
      put a jtests.py module in your application directory, and any function in it that
    starts with "is_" will be added to the tests dictionary of the environment without
    the "is_" prefix, (so is_odd will be called "odd")
 
 
    To use this, i put it in a file called jinja.py and if I need to respond to an http
    request with a page that's rendered in jinja, I just say:
    # from myproj.jinja import jrespond
    and when I return a response:
    # return jrespond( template_file_name, context )
"""


from django.conf import settings
from django.http import HttpResponse
from jinja2 import Environment, FileSystemLoader, PackageLoader, ChoiceLoader, exceptions as jexceptions

def import_module(name):
    mod = __import__(name)
    components = name.split('.')
    for comp in components[1:]:
        mod = getattr(mod, comp)
    return mod

# environment setup:    
#  the loader here searches "template dirs", plus any templates folder in any installed app
#  autoescape set to true explicitly, because in Jinja2 it's off by defualt
jenv = Environment( autoescape=True, loader = ChoiceLoader( [FileSystemLoader(settings.TEMPLATE_DIRS)] + [PackageLoader(app) for app in settings.INSTALLED_APPS] ) )

# Search for and load application specific functions, filters and tests.
for app in settings.INSTALLED_APPS: #import jtemp.py from each app and add it to globals
    try:
        jtemp = import_module( app + '.jtemp' )
        app_name = app.split('.')[-1] #application name
        #if jtemp defines jinja_name, use it as the module name in jinja, otherwise use application name
        name = getattr( jtemp, 'jinja_name', app_name ) 
        jenv.globals[name] = jtemp
    except ImportError:
        pass
    try:
        filters = import_module( app + '.jfilters' )
        filter_functions = [getattr(filters,filter) for filter in dir(filters) if callable(getattr(filters,filter)) and filter.startswith("filter_")]
        for func in filter_functions:
            jenv.filters[getattr(func, 'jinja_name', func.__name__[7:])] = func            
    except ImportError:
        pass
    try:
        tests = import_module( app + '.jtests' )
        test_functions = [getattr(tests,test) for test in dir(tests) if callable(getattr(tests,test)) and test.startswith("is_")]
        for func in test_functions:
            jenv.tests[getattr(func, 'jinja_name', func.__name__[3:])] = func            
    except ImportError:
        pass
        
def get_select_template( filenames ):
    """ get or select template; accepts a file name or a list of file names """
    if type( filenames ) == type([]):
        for file in filenames:
            try: return jenv.get_template( file )
            except jexceptions.TemplateNotFound: pass
        raise jexceptions.TemplateNotFound( filenames )
    else:
        file = filenames #for better readability
        return jenv.get_template( file )

def jrender(filename, context={}):
    """ renders a jinja template to a string """
    template = get_select_template(filename)
    return template.render(**context)
    
def jrespond( filename, context={}, request=None ):
    """ renders a jinja template to an HttpResponse """
    if( request ):
        context['request'] = request
    return HttpResponse( jrender( filename, context ) )

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 3 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 7 months ago

Comments

hasenj (on October 6, 2008):

It's not just the DRY principle (I myself have written some boilerplate code/decorators to make writing tags easier) but it's still bothersome to write tags, and still hard to do very simple logic in templates. for example, consider having a list of objects (say, pictures, with titles and thumbnails, like the results from google image search) and you want to arrange them in a way, say, 3 pictures per row. In Jinja you just check {% if loop.index is divisibleby(3) %} to start a new row. In django, you always have to create a new tag for every tiny bit of logic that's not included in the set of default tags/filters.

#

Please login first before commenting.