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
- Converting an existing Django model to Django-MPTT
- Using a SQL database to mimic a filesystem will, eventually, create bottlenecks when it comes to traversing the filesystem. One solution is modified preordered tree traversal, which saves the tree structure in an easily-used manner inside the model.
- Two search bookmarklets for Django
- Bookmarklets—JavaScript code in a bookmark—can make working with big Django databases much easier.
- 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.
- 29 more pages with the topic Django, and other related pages
More PDF
- Creating searchable PDFs in Ventura
- My searchablePDF script’s behavior changed strangely after upgrading to Ventura. All of the pages are generated at extremely low quality. This can be fixed by generating a JPEG representation before generating the PDF pages.
- Create searchable PDFs in Swift
- This Swift script will take a series of image scans, OCR them, and turn them into a PDF file with a simple table of contents and searchable content—with the original images as the visually readable content.
- 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.
- Four more pages with the topic PDF, and other related pages
More Python
- Quick-and-dirty old-school island script
- Here’s a Python-based island generator using the tables from the Judges Guild Island Book 1.
- Astounding Scripts on Monterey
- Monterey removes Python 2, which means that you’ll need to replace it if you’re still using any Python 2 scripts; there’s also a minor change with Layer Windows and GraphicConverter.
- Goodreads: What books did I read last week and last month?
- I occasionally want to look in Goodreads for what I read last month or last week, and that currently means sorting by date read and counting down to the beginning and end of the period in question. This Python script will do that search on an exported Goodreads csv file.
- Test classes and objects in python
- One of the advantages of object-oriented programming is that objects can masquerade as each other.
- Timeout class with retry in Python
- In Paramiko’s ssh client, timeouts don’t seem to work; a signal can handle this—and then can also perform a retry.
- 30 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