"""Make data-driven plots on the web using your Django data.
This module contains only the core file management backend. Plotting
code is found in dataplot.*, where * is one of {R,matplotlib,etc.}
"""
import os,sys,pdb
try:
import Image
except ImportError:
raise ImportError("Django-dataplot requires that you install the Python Imaging Library, go get it at www.pythonware.com/products/pil/")
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
autosave=False
# Set defaults for convert location, can override later
view_program='display'
def __init__(self,basename,tocall,**kwargs):
"""Make a new plot to display.
Required args: (no sensible defaults)
basename: basename of this plot (no .pdf)
tocall: makes data dict 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.replace(" ","_")
self.tocall=tocall
for k in kwargs:
setattr(self,k,kwargs[k])
def get_plot_args(self):
"""Gather input data from defaults and return value of tocall.
1. defaults for this plot type
2. returned arguments from get_plot_args
"""
dict_precedence=[
getattr(self,'default_kwargs',None),
self.tocall(),
]
kwargs={}
for D in dict_precedence:
if D:
if type(D)!=dict:
raise ValueError, 'data must be specified as a dict!\n'\
'Got: %s'%plotargs
kwargs.update(D)
return kwargs
def __repr__(self):
return '<%s.%s: %s.%s.%s, %s>'%(
self.__class__.__module__,
self.__class__.__name__,
self.tocall.im_class.__module__,
self.tocall.im_class.__name__,
self.tocall.__name__,
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:
emsg=getattr(self,'ERROR_MESSAGE','')
if emsg:
raise PlotError,emsg,sys.exc_traceback
else:
raise PlotError,'%s: %s when trying to make %s'%(
e.__class__.__name__,e,self), sys.exc_traceback
urls=self.prefix(settings.MEDIA_URL)
for k in urls:
urls[k]=urls[k].replace("\\","/")
return urls
def makefiles(self):
"""Make initial file and conversions.
makefile() just makes the initial file.
"""
self.makedirs()
self.makefile()
self.convert()
self.enable_caching=True
def makedirs(self):
"""Make subdirectories of media/ for this plot.
"""
try:
D,f=os.path.split(self.from_filename())
os.makedirs(D)
newdir=self.basename.split(os.path.sep)[0]
ND=os.path.join(settings.MEDIA_ROOT,newdir)
cmd="chgrp -hR %s %s ; chmod g+w -R %s"%(self.chgrp,ND,ND)
os.system(cmd)
except OSError:
pass
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:
# store enable_caching value
# so we only have to make the plot once
# even if the template calls get_urls more than once
old=self.enable_caching
html=render_to_string('dataplot/'+tfile,{'plot':self})
# then recall the old value of enable_caching
self.enable_caching=old
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 from_url(self):
return self.get_urls()[self.convert_from]
def get_filenames(self):
"""Return dictionary of image filenames.
"""
files=self.prefix(os.path.join(settings.MEDIA_ROOT,''))
if os.path.sep=='\\':
for k in files:
files[k]=files[k].replace("/",'\\')
return files
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 source to other formats using pil.
"""
filenames=self.get_filenames()
src=filenames.pop(self.convert_from)
i=Image.open(src)
for k in filenames:
new=i.copy()
size=self.convert_to[k].get('size','')
if size:
new.thumbnail(size,Image.ANTIALIAS)
new.save(filenames[k])
# 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().values():
self.do_chgrp_on(fn)
def do_chgrp_on(self,fn):
perm_cmd='chmod g+w %s ; chgrp %s %s'%(
fn,self.chgrp,fn)
os.system(perm_cmd)
def view(self,k=None):
"""Use some other program to look at rendered source image.
Will make the source image if it does not exist yet.
"""
filename=k and self.get_filenames()[k] or self.from_filename()
if not os.path.exists(filename):
self.makefiles()
cmd='%s %s'%(self.view_program,filename)
os.system(cmd)