"""Making data-driven plots on the web using your Django data.
This module contains only the core file management backend. Plotting
code is found in django.contrib.dataplot.*, where * is one of
{R,pil,etc.}
"""
import os,sys,pdb
from django.conf import settings
from django.template.loader import render_to_string
from django.utils.safestring import mark_safe
class PlotError(Exception): pass
class GenericPlot(object):
"""Singular representation of a plot for the web.
Subclasses need to define:
convert_to: dictionary that defines associated file suffixes, i.e.
convert_to={
'png':{'suffix':'.png'},
'thumb':{'suffix':'-thumb.png','convert_args':'-resize 65x90'},
'pdf':{'suffix':'.pdf'},
}
convert_from: one of the keys from convert_to, which specifies
which of the files is created by the makefile method.
makefile: a method that creates the image specified by
convert_from on the filesystem.
"""
# Default values can be overridden
# by subclasses or after instantiation
# If unspecified, try to get default image cache setting
enable_caching = not settings.DEBUG
chgrp=None
# Set defaults for convert location, can override later
convert_binary='convert'
view_program='display'
def __init__(self,basename,get_plot_args,**kwargs):
"""Make a new plot to display.
Required args: (no sensible defaults)
basename: basename of this plot (no .pdf)
get_plot_args: makes data to pass to the function for plotmaking.
Other plot parameters may be specified after instantiation as
attributes:
enable_caching: should the image be remade every time?
chgrp: group to set write permissions for
"""
self.basename=basename
self.get_plot_args=get_plot_args
for k in kwargs:
setattr(self,k,kwargs[k])
def __repr__(self):
return '<%s: %s, %s>'%(
self.__class__.__name__,
self.get_plot_args,
self.basename,
)
def prefix(self,pre):
"""Generalized form of url/filename reporting.
pre: prefix to attach to the basename and suffix.
"""
di=dict(self.convert_to)
return dict([(k,pre+self.basename+di[k]['suffix']) for k in di])
def get_urls(self):
"""Return dictionary of image URLs.
Make the plot if it doesn't exist or caching is off.
"""
files_dont_exist=sum([
not os.path.exists(fn) for fn in self.get_filenames().values()])
if not self.enable_caching or files_dont_exist:
try:
self.makefiles()
# Need to raise unique exception here so django doesnt catch it
# provide sys.exc_traceback so we can see full traceback
except Exception, e:
raise PlotError,'%s: %s when trying to make %s'%(
e.__class__.__name__,e,self), sys.exc_traceback
return self.prefix(settings.MEDIA_URL)
def makefiles(self):
"""Make initial file and conversions.
makefile() just makes the initial file.
"""
self.makefile()
self.convert()
self.enable_caching=True
def to_html(self):
"""Render the PNG image and link to the PDF.
"""
return self.render_html('to_html.html')
def to_html_thumb(self):
"""Render the PNG thumb.
"""
return self.render_html('to_html_thumb.html')
def to_html_nolink(self):
"""Render the PNG without link to PDF.
"""
return self.render_html('to_html_nolink.html')
def render_html(self,tfile):
"""Render a HTML template with self as context 'plot'.
"""
try:
html=render_to_string('dataplot/'+tfile,{'plot':self})
except PlotError, e:
if settings.DEBUG:
raise
else: # for production version show error on page
html=render_to_string("dataplot/error.html",
{'e':e,'etype':e.__class__.__name__}))
return mark_safe(html)
def from_filename(self):
return self.get_filenames()[self.convert_from]
def get_filenames(self):
"""Return dictionary of image filenames.
"""
return self.prefix(settings.MEDIA_ROOT+'/')
def get_full_base(self):
suffix=self.convert_to[self.convert_from]['suffix']
return self.get_filenames()[self.convert_from][:-len(suffix)]
def convert(self):
"""Convert from PDF to other formats using ImageMagick.
"""
filenames=self.get_filenames()
src=filenames.pop(self.convert_from)
for k in filenames:
cargs=self.convert_to[k].get('convert_args','')
convert_cmd=' '.join([
self.convert_binary,
cargs,
src,
filenames[k],
])
os.system(convert_cmd)
# Finally ensure permissions for group if requested
self.do_chgrp()
def do_chgrp(self):
"""Change group write perms if requested.
This is useful if your testing and production webservers have
different users but share the same media directory.
"""
if self.chgrp:
for fn in self.get_filenames():
perm_cmd='chmod g+w %s ; chgrp %s %s'%(
fn,self.chgrp,fn)
os.system(perm_cmd)
def get_app_dirs(self):
"""Dig through settings.INSTALLED_APPS for full paths.
"""
return [os.path.dirname(
__import__(mn,[],[],'.'.split(mn)[-1]).__file__)
for mn in settings.INSTALLED_APPS]
def view(self):
"""Use some other program to look at rendered source image.
Will make the source image if it does not exist yet.
"""
filename=self.from_filename()
if not os.path.exists(filename):
self.makefile()
cmd='%s %s'%(self.view_program,filename)
os.system(cmd)