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.
- 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 Python
- Multiple Input Fields with multiple inheritance
- We needed to display one TextField as either a TextInput or a Textarea, depending on the value in the field. Multiple inheritance makes it easy, if a bit wonky.
- PyTown
- General rambling in code regarding Python, Mailman, and Django.
- Thinking Python: Django cache expiration time
- Django sets the expiration time when data is cached. Sometimes it makes more sense to expire data dynamically based on later changes to the database. Does this mean a change to CacheClass? Not necessarily.
- Django Twitter tag and RSS object
- I wanted to embed my twitter feed into my Django blog, and didn’t see any simple RSS readers for Python that did what I wanted.
- Excerpting partial XHTML using minidom
- You can use xml.dom.minidom to parse partial XHTML as long as you use a few tricks and don’t mind that getElementById doesn’t work.
- 18 more pages with the topic Python, and other related pages
More Django
- Reusing Django’s filter_horizontal
- Just as with pop-ups, it’s possible to use the built-in JavaScript for filtering multiple-selection popups on custom forms.
- Django formsets and date/time fields
- Date/Time fields in Django formsets appear to have incompatible default values, resulting in forms using them always looking as though they’ve got a new entry when they don’t.
- Multiple Input Fields with multiple inheritance
- We needed to display one TextField as either a TextInput or a Textarea, depending on the value in the field. Multiple inheritance makes it easy, if a bit wonky.
- Django tutorial mostly ready
- My long-promised Django tutorial is pretty much ready. It’s still designed around an in-person tutorial, but you should be able to get started using it even if you’re on your own.
- Django: Beyond the SQL
- Django is a great application framework for Python and web applications. You can use it to greatly speed up your database and application development both on the web and on the command line. This tutorial is currently a very rough draft; it probably won’t be very useful without the assistance of someone who knows Django running the tutorial. If I ever run this tutorial a second time, I’ll probably update it with screenshots to make it more usable for individuals.
- 22 more pages with the topic Django, and other related pages
More PDF
- 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.
- Combining multiple PDF files into a single file
- Automator allows you to combine multiple PDF files into a single file.
- One more page with the topic PDF, and other related pages

Update: I forgot to include the lines that import the functionality necessary to register the tag.