Using Content-Disposition, Django decorators, and ssi to enable users to download html files

I wanted people to be able to download hatch templates as html files so they can use them for off-line use.

  • This should be a single file (no external css, js, or images)
  • The server should not have to write any files to it's own disk
  • I don't want to write more code than I have to
  • The user shouldn't have to right-click "Save As"

First Order of business: Embedding css and javascript in the template itself

I wanted users to only have to download a single file (no external css or javascript) so I looked at the ssi template tag. Create a new url that will be used to download the html file

#urls.py
...
(r'^templates/download/(?P[\w\-]+)/$', 'hatch.eggs.views.download_template' ),
...

Create a view to be used for these requests:

#/hatch/eggs/views.py
...
def download_template(request, slug):
        template = Template.objects.get(slug=slug)
        template_vars = TemplateVariable.objects.filter(template=template).order_by("id")

        return render_to_response("download_template.html",{"template":template, "template_vars":template_vars},context_instance=RequestContext(request))
...

Create a html template and load ssi:

#hatch/templates/download_template.html
<body>
<head>
   <style>
   {% load ssi from future %}
   {% ssi '/var/www/dev.hatchconfigs.com/hatch/media/eggs.css' %}
   </style>
</head>
<body>
<script type="text/javascript">
  {% ssi '/var/www/dev.hatchconfigs.com/hatch/media/jquery-1.6.1.min.js' %}
</script>

<script type="text/javascript">
   {% ssi '/var/www/dev.hatchconfigs.com/hatch/media/hatch.js' %}
</script>
...
</body>
#settings.py:
#this tells django what paths you should expose for server side includes, without it you cannot do ssi.
ALLOWED_INCLUDE_ROOTS= ('/var/www/dev.hatchconfigs.com/hatch/media')

I browsed to my url and confirmed that all css and js were included in the single download_template.html file.

Second: Supplying Content-Type, Content-Disposition http headers with django

After looking at a couple of examples pyText2Pdf, outputting-pdf you see a pattern of using "Content-Disposition."  My buddy Ian sent me a link to this article.  I came across this snippet which allows you to insert any http header you want via a view decorator.  I tried it out and it works... but I wanted something a little more tailored for my needs so I wrote my own decorator:

#eggs/decorators.py
def downloadable(supplied_filename=None):
   def _decorator(viewfunc):
      def _closure(request, *args, **kwargs):
         #if we supply a filename use it,
         if supplied_filename:
            filename = filename
         #otherwise use the slug field from the model being used
         else:
            if 'slug' in kwargs:
               filename  = kwargs['slug'] + ".html"
         response = viewfunc(request, *args, **kwargs)
         #set content-type
         response['Content-type']= 'text/html'
         #set disposition and filename
         response['Content-Disposition']='attachment; filename=%s' % (filename)
         return response
      return wraps(viewfunc)(_closure)
   return _decorator

Third: Tying it all together

  • css and js files included in a single htmlm file -- check
  • custom decorator to derive filename and set http headers -- check

Now we have to use the decorator in our view:

# hatch/eggs/views.py
from hatch.eggs.decorators import *
...
@downloadable()
def download_template(request, slug):
        template = Template.objects.get(slug=slug)
        template_vars = TemplateVariable.objects.filter(template=template).order_by("id")
        return render_to_response("download_template.html",{"template":template, "template_vars":template_vars},context_instance=RequestContext(request))
...

Now whenever someone browses to /templates/download/some_slug/ the browser will ask if you want to save the file named some_slug.html, yay!

Tagged as django , headers html
Written by Andrew Konkol on February 28th, 2012

0 Comments

Log in with Twitter, Google, Facebook, LinkedIn to leave a comment.