mtt2p
3/15/2013 - 9:48 AM

Render LaTeX code generated with Django templates to a PDF response.

Render LaTeX code generated with Django templates to a PDF response.

\documentclass[a4paper]{article}

% Cyrillic support
\usepackage[T2A]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[english,russian]{babel}
\DeclareSymbolFont{T2Aletters}{T2A}{cmr}{m}{it}

% PDF search & cut'n'paste
\usepackage{cmap}

% Page options
\usepackage{geometry}
\geometry{left=1cm}
\geometry{right=1cm}
\geometry{top=1cm}
\geometry{bottom=1.25cm} 

% Misc options
\setcounter{secnumdepth}{5}
\usepackage[parfill]{parskip}

\nonstopmode

{% block extraheader %}{% endblock %}

\title{% templatetag openbrace %}{% block title %}{% endblock %}}
\author{% templatetag openbrace %}{% block author %}{% endblock %}}
\date{% templatetag openbrace %}{% block date %}{% endblock %}}

\begin{document}
{% block document %}{% endblock %}
\end{document}
from django.template.loader import get_template
from django.template import TemplateDoesNotExist, Context
from django.http import HttpResponse, Http404, HttpResponseNotModified
from django.core.cache import cache
from django.conf import settings

from tempfile import mkdtemp
import subprocess
import os
import shutil
from hashlib import md5


TEMP_PREFIX = getattr(settings, 'TEX_TEMP_PREFIX', 'render_tex-')
CACHE_PREFIX = getattr(settings, 'TEX_CACHE_PREFIX', 'render-tex')
CACHE_TIMEOUT = getattr(settings, 'TEX_CACHE_TIMEOUT', 86400)  # 1 day


def render_tex(request, template, ctx={}):
    doc = template.rsplit('/', 1)[-1].rsplit('.', 1)[0]

    try:
        body = get_template(template).render(Context(ctx)).encode("utf-8")
    except TemplateDoesNotExist:
        raise Http404()

    etag = md5(body).hexdigest()
    if request.META.get('HTTP_IF_NONE_MATCH', '') == etag:
        return HttpResponseNotModified()

    cache_key = "%s:%s:%s" % (CACHE_PREFIX, template, etag)
    pdf = cache.get(cache_key)
    if pdf is None:
        if '\\nonstopmode' not in body:
            raise ValueError("\\nonstopmode not present in document, cowardly refusing to process.")

        tmp = mkdtemp(prefix=TEMP_PREFIX)
        try:
            with open("%s/%s.tex" % (tmp, doc), "w") as f:
                f.write(body)
            del body

            error = subprocess.Popen(
                ["pdflatex", "%s.tex" % doc],
                cwd=tmp,
                stdin=open(os.devnull, "r"),
                stderr=open(os.devnull, "wb"),
                stdout=open(os.devnull, "wb")
            ).wait()

            if error:
                if request.user.is_superuser:
                    log = open("%s/%s.log" % (tmp, doc)).read()
                    return HttpResponse(log, mimetype="text/plain")
                else:
                    raise RuntimeError("pdflatex error (code %s) in %s/%s" % (error, tmp, doc))

            pdf = open("%s/%s.pdf" % (tmp, doc))
        finally:
            shutil.rmtree(tmp)

        if pdf:
            cache.set(cache_key, pdf, CACHE_TIMEOUT)

    res = HttpResponse(pdf, mimetype="application/pdf")
    res['ETag'] = etag
    return res