In this example we're going to take a closer look at the flexibility of Django's loose coupling philosophy by replacing its default templating language with Jinja2. Jinja2 is a very similar template language to the one provided by Django, but provides additional functionality like more flexible if syntax in templates, the option to raise an error when an undefined object is operated upon within templates (as opposed to Django's templating language which will always fail silently), and more flexible solutions for retrieving templates.
Lets get started.
First we need to download and install Jinja2.
sudo easy_install jinja2
Then make sure it works.
import jinja2
Create a Django project and app.
django_admin.py startproject loose_coupling cd loose_coupling python manage.py startapp with_jinjaCreate a templates folder for your project, and one for the new app as well.
mkdir templates mkdir with_jinja/templates mkdir with_jinja/templates/with_jinja
Open up
loose_coupling/settings.pyand change the settings forINSTALLED_APPSandTEMPLATE_DIRS.INSTALLED_APPS = ( 'loose_coupling.with_jinja', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', ) import os ROOT_PATH = os.path.dirname(__file__) TEMPLATE_DIRS = ( os.path.join(ROOT_PATH,'templates'), os.path.join(ROOT_PATH,'with_jinja/templates'), )
Open up
loose_coupling/urls.pyand change its contents to this:from django.conf.urls.defaults import * urlpatterns = patterns( 'loose_coupling', (r'^$','with_jinja.views.index'), )
Now we just need to create our views. First, open up
with_jinja/views.pyand add these imports:import math,random from django.http import HttpResponse from django.conf import settings from jinja2 import FileSystemLoader, Environment
And then we're going to write a little convenience method to handle rendering via Jinja2.
template_dirs = getattr(settings,'TEMPLATE_DIRS') default_mimetype = gettattr(settings, 'DEFAULT_CONTENT_TYPE') env = Environment(loader=FileSystemLoader(template_dirs)) def render_to_response(filename, context={},mimetype=default_mimetype): template = env.get_template(filename) rendered = template.render(**context) return HttpResponse(rendered,mimetype=mimetype)
And next, lets create a simple custom test that our Jinja template is going to use to help render its output.
def greater_than_fifty(x): return x > 50 env.tests['gtf'] = greater_than_fifty
Now lets create the
indexview that we'll be using.def index(request): n = int(math.floor(100 * random.random())) return render_to_response('with_jinja/index.html',{'n':n})
As you can see, its a very simple view, merely passing a generated number to the
render_to_responsefunction we created before.Next, we need to create our
index.htmltemplate that theindexview is trying to render. To do that, first we are going to create abase.htmltemplate in theloose_coupling/templatesdirectory. So, open uploose_coupling/templates/base.htmland add this to it<html> <head> <title> Loosely Coupled Django </title> </head> <body> {% block content %} {% endblock %} </body> </html>
And now we need to create the
loose_coupling/with_jinja/templates/with_jinja/index.htmltemplate. It will extend our base template and be quite simple:{% extends 'base.html' %} {% block content %} <p> The number generated was {{ n }}. </p> {% if n is gtf %} <p> The number was definitely greater than fifty.</p> {% else %} <p> Unfortunately, the number was below fifty.</p> {% endif %} {% endblock %}
Now, save all the files and run the project.
python manage.py runserver
Now navigate over to http://127.0.0.1:8000 and you'll see Django development server merrily rendering our Jinja2 template.
(You can download a zip of the project we developed here.)
Thanks to Django's loose coupling philosophy, it was really quite painless to switch over to using Jinja2 instead of the default templating language. As this series continues we'll look at how this isn't just a freak occurance, but occurs throughout the Django stack.
Hey, you might want to reread you post and replace the occurrences of Jinga with Jinja ;)
Um, yeah, sorry about that. In my lame defense I am packing up my apartment to go back to the US after living in Japan for a year and am kind of operating at maybe 37% of full mental capacity.
Thanks for pointing that out. :)
Nice series, I really like it!
Maybe you should also add the 'index' view, so that it becomes clear why your importing math and random.
I am ashamed of myself. I managed to leave out the most important piece of information. edit edit
Fixed. Thanks for pointing out the failure Mike. I promise to proofread the next one better. :/
hi will,
many thanks to this entry! I really love django and jinja2 and now I can use both of them. Thanks for showing it how. I have some questions though...
in the settings.py
TEMPLATE_DIRS = ( os.path.join(ROOT_PATH,'templates'), os.path.join(ROOT_PATH,'with_jinja/templates'), )
then we have
template_dirs = getattr(settings,'TEMPLATE_DIRS') default_mimetype = gettattr(settings, 'DEFAULT_CONTENT_TYPE') env = Environment(loader=FileSystemLoader(template_dirs))
the template is save in with_jinja/templates/with_jinja ... Does this mean that django can find template on sub directory?
Thanks
James,
Unfortunately this was unclear because I boneheadedly left out the
indexview. Jinja2, like the standard Django templating system, can find templates using an path from the base template directory. Thus the code to render the template here is like this:and the template locator thus checks for the file at these two locations:
So, it can find files in subdirectories, if you explicitly tell it to do so. Let me know if you have any other questions.
Thank you for the post. But real "loose coupling" would be the ability to switch the template engine in admin and in generic views as well. I don't think that's easily doable...
What you are describing is not loose coupling but instead a complete lack of coupling. In this specific case (pluggable rendering for generic views and admin) it wouldn't be hard to allow complete decoupling, but chasing that level of decoupling ends up with endless layers of abstraction. Django places value on loose coupling, but it isn't the sole design principle either. It would be a very different framework (err, group of tools bundled together purely by happenstance) if complete decoupling was the developer's battlecry.
Reply to this entry