Embedding Mako into Django
This is my second step in generating PDF dynamically in Django via Python. In PDF and Python I wrote about how to use ReportLab to dynamically generate simple PDF documents. As part of getting Django to produce dynamic PDF, I also hoped to be able to embed Python itself into Django’s templates.
The first thing I tried to do was work with Python’s built-in eval and compile functionality, but that was a dismal failure. I could barely get it to work on a single line of code, let alone work with multiple lines of code. Today, however, it occurred to me that there must be tools to embed Python into text (i.e., PHP-like Python), and that if one of them would accept a string of text, I could extend Django’s template language to accept a block of that other template language.
It may seem a little strange to embed Python into a template language that is itself implemented in Python. But because Django is implemented in Python, it is sometimes easier to work with the data it holds by using Python. In this extension, for example, I will need to pass the context that Django sends, which in turn consists of all of the Django models in use on this page.
Myghty and Mako were the first that came up in a Google search, and as far as I can tell only Mako accepts strings easily. Sealing the deal was that the very front page of Mako’s web site told me exactly what I needed to do:
- from mako.template import Template
- print Template("hello ${data}!").render(data="world")
It seemed obvious that making a Django block tag to accept Mako templating code would be dead easy, and it was. The hardest part was figuring out how to send the context as parameters. (Turn it into a dict and preface it with ** in the method call.)
Here’s the entire code to create a mako block in Django:
[toggle code]
- from django import template
- import mako.template as mako
-
def do_mako(parser, token):
- nodelist = parser.parse(('endmako',))
- parser.delete_first_token()
- return makoNode(nodelist)
-
class makoNode(template.Node):
-
def __init__(self, nodelist):
- self.nodelist = nodelist
-
def render(self, context):
- block = self.nodelist.render(context)
- #turn the context into a dict
- parameters = {}
- [parameters.update(parameter) for parameter in context]
- #render with Mako
- rendered = mako.Template(block, format_exceptions=True).render(**parameters)
- return rendered
-
def __init__(self, nodelist):
- register = template.Library()
- register.tag('mako', do_mako)
Save it as something like “musicalfish.py” in a folder called “templatetags” in your app’s folder. (You’ll need to also create a blank __init__.py to turn the templatetags folder into a Python package.) Load it into your Django template as you would any other extension, something like:
- {% load musicalfish %}
at the top of your template file.
Once you have that loaded, you can do things like:
[toggle code]
- {% mako %}
- <dl>
-
%for child in page.children:
- <dt>${child.title}</dt>
- <dd>${child.description}</dd>
- %endfor
- </dl>
- {% endmako %}
The “page” variable is part of the context that my Django pages receive; it contains most of the page’s content.
Mako’s syntax is simple. Each of those lines after the % is a standard Python control block. Each item inside ${…} is standard Python. Whatever it returns will be displayed. And if you want to drop completely into Python, you can even use <% and %> to mark it off.
Note that this mako tag actually runs the block through both Django’s parser and Mako’s. That worries me. Although as far as I can tell the languages don’t overlap, they might in the future. But I don’t know how to get the text out of nodelist without rendering it.
Using Mako to generate PDF dynamically in Django
The first issue I ran into is that, as far as I can tell, you aren’t allowed to print inside of a Python block. Mako doesn’t collect printed output and append it to the returned text. Any text that you want to display you’ll need to collect (say, in a list) and then display it using ${}.
This can cause problems with methods that expect to handle their own I/O, such as ReportLab Toolkit. Reportlab doesn’t do any output except write to a file-like object. However, it’s fairly easy to construct a simple object that can be written to as a file using StringIO.
The bigger problem is ascii encoding. Mako seems to expect that everything coming through is text of some sort (or, more likely, I just don’t understand how to handle encoding). This doesn’t seem to be a problem with PDF, but I wonder if it will allow image creation. That’s an issue for another day, however. I don’t need it now.
What I want now is to know that I can create PDF dynamically in a Django template. Here’s a simple example of creating a PDF page via the mako tag:
- {% mako %}
- <%
- from reportlab.platypus import Paragraph, SimpleDocTemplate
- from reportlab.lib.styles import getSampleStyleSheet
- from reportlab.lib.pagesizes import letter
- import StringIO
- destination = StringIO.StringIO()
- style = getSampleStyleSheet()
- pdf = SimpleDocTemplate(destination, pagesize=letter)
- posts = []
- posts.append(Paragraph(page.title, style["Heading1"]))
- posts.append(Paragraph(page.description, style["Normal"]))
- pdf.build(posts)
- pdfpage = destination.getvalue().decode("ascii", "ignore")
- %>
- ${pdfpage}
- {% endmako %}
Since it’s generating PDF, that is the entire template. Obviously, I also have to have Django return the correct mimetype, application/pdf. For my site, I have a database of templates. Each page knows what template it uses, and each template record includes the mimetype it should return. So what I end up doing is something like this in views.py:
[toggle code]
-
def makeDetail(page):
- pcontext = commonContext(page)
- template, mimetype = page.template()
- r = render_to_response(template, pcontext)
- r.headers['Content-Type'] = mimetype + "; charset=utf-8"
- return r
Which means that I end up with this header:
- Content-Type: application/pdf; charset=utf-8
Next up, I need to learn how to create multiple columns without having to do the measurements myself.
Update: I forgot to include the lines that import the functionality necessary to register the tag.
- Django
- “Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design.” Oh, the sweet smell of pragmatism.
- ReportLab Toolkit
- “The ReportLab Open Source PDF library is a proven industry-strength PDF generating solution, that you can use for meeting your requirements and deadlines in enterprise reporting systems.”
- Mako
- “Mako is an embedded Python language, which refines the familiar ideas of componentized layout and inheritance to produce one of the most straightforward and flexible models available, while also maintaining close ties to Python calling and scoping semantics.”
- StringIO—Read and write strings as files
- “This module implements a file-like class, StringIO, that reads and writes a string buffer (also known as memory files). See the description of file objects for operations.”
More Django
- Fixing Django’s feed generator without hacking Django
- It looks like it’s going to be a while before the RSS feed generator in Django is going to get fixed, so I looked into subclassing as a way of getting a working guid in my Django RSS feeds.
- ModelForms and FormViews
- This is just a notice because when I did a search, nothing came up. Don’t use ModelForm with FormView, use UpdateView instead.
- Django: fix_ampersands and abbreviations
- The fix_ampersands filter will miss some cases where ampersands need to be replaced.
- Custom managers for Django ForeignKeys
- I’ve got one really annoying model for keywords. There’s one category of keywords that, by default, should not show up when used as a ForeignKey for most models. Key word: most.
- Fixing Django 1.2.4’s SuspiciousOperation on filtering
- When you get the message “Filtering by keyword not allowed” in Django 1.2.4, here’s one way to fix it.
- 27 more pages with the topic Django, and other related pages
More PDF
- Quality compressed PDFs in Mac OS X Lion
- The instructions for creating a “reduce PDF file size” filter in Lion are the same as for earlier versions of Mac OS X—except that for some reason ColorSync saves the filter in the wrong place (or, I guess, Preview is looking for them in the wrong place).
- Calculating true three-fold PDF in Python
- Calculating a true three-fold PDF requires determining exactly where the folds should occur.
- Adding links to PDF in Python
- It is very easy to add links to PDF documents using reportlab or platypus in Python.
- Multiple column PDF generation in Python
- You can use ReportLab’s Platypus to generate multi-column PDFs in Snakelets, Django, or any Python app.
- Python PDF generation with Snakelets
- One of the things I need to do to move my current web site over to Django is be able to automatically generate PDF documents. Step is to learn how to generate PDF using Python.
- Two more pages with the topic PDF, and other related pages
More Python
- Parsing JSKit/Echo XML comments files
- While I’m not a big fan of remote comment systems for privacy reasons, I was willing to use JSKit as a temporary solution because they provide an easy XML dump of posted comments. This weekend, I finally moved my main blog to custom comments; here’s how I parsed JSKit’s XML file.
- Put a relative clock on your Desktop with GeekTool
- There are a lot of desktop clocks that show the absolute time. But sometimes you just want to know if the time is today, or yesterday, or two days ago. Here’s how to do it with Python and GeekTool.
- Multiple tables on the same command
- The way the “random” script currently stands, it does one table at a time. Often, however, you have more than one table you know you’re going to need. Why not use one command to rule them all?
- Easier random tables
- Rather than having to type --table and --count, why not just type the table name and an optional count number?
- Programming for Gamers: Choosing a random item
- If you can understand a roleplaying game’s rules, you can understand programming. Programming is a lot easier.
- 24 more pages with the topic Python, and other related pages

I may be wrong, but this seems like you shouldn't be embedding Python in the templates at all. Instead, you should put the Python code in the view code (views.py) and then just put {{ pdfpage }} in the template. Also, for embedding Mako directly you could use http://www.djangosnippets.org/snippets/97/ or http://fuzzythinker.blogspot.com/2007/04/using-mako-in-django.html
Fahrzin Hemmati at 10:18 p.m. October 21st, 2009
LxAXX
Yeah, making a template that generates PDF on its own is probably not the best example of what this is useful for. What I want is the ability to switch between Django's normal templating and Mako's within a page; that is, most of the time I want Django's simpler template code. But every once in a while I want to drop into Python. The examples you give seem to be for replacing Django's templating with Mako. Making a template file that itself generates PDF is kinda cool, but not recommended.
capvideo at 12:50 a.m. November 4th, 2009
tVAhq
Mako templates can be easily added through django-mako module:
http://code.google.com/p/django-mako/wiki/Usage
Saleem Ansari at 9:19 a.m. August 13th, 2010
Z4tPT