00001 """Make data-driven plots on the web using your Django data.
00002
00003 This module contains only the core file management backend. Plotting
00004 code is found in dataplot.*, where * is one of {R,matplotlib,etc.}
00005
00006 """
00007
00008 import os,sys,pdb
00009 from django.conf import settings
00010 from django.template.loader import render_to_string
00011 from django.utils.safestring import mark_safe
00012
00013 class PlotError(Exception): pass
00014
00015 class GenericPlot(object):
00016 """Singular representation of a plot for the web.
00017
00018 Subclasses need to define:
00019
00020 convert_to: dictionary that defines associated file suffixes, i.e.
00021 convert_to={
00022 'png':{'suffix':'.png'},
00023 'thumb':{'suffix':'-thumb.png','convert_args':'-resize 65x90'},
00024 'pdf':{'suffix':'.pdf'},
00025 }
00026
00027 convert_from: one of the keys from convert_to, which specifies
00028 which of the files is created by the makefile method.
00029
00030 makefile: a method that creates the image specified by
00031 convert_from on the filesystem.
00032
00033 """
00034
00035
00036
00037
00038 enable_caching = not settings.DEBUG
00039 chgrp=None
00040 autosave=False
00041
00042 convert_binary='convert'
00043 view_program='display'
00044
00045 def __init__(self,basename,tocall,**kwargs):
00046 """Make a new plot to display.
00047
00048 Required args: (no sensible defaults)
00049
00050 basename: basename of this plot (no .pdf)
00051 tocall: makes data dict to pass to the function for plotmaking.
00052
00053 Other plot parameters may be specified after instantiation as
00054 attributes:
00055
00056 enable_caching: should the image be remade every time?
00057 chgrp: group to set write permissions for
00058
00059 """
00060 self.basename=basename.replace(" ","_")
00061 self.tocall=tocall
00062 for k in kwargs:
00063 setattr(self,k,kwargs[k])
00064
00065 def get_plot_args(self):
00066 """Gather input data from defaults and return value of tocall.
00067
00068 1. defaults for this plot type
00069 2. returned arguments from get_plot_args
00070
00071 """
00072 dict_precedence=[
00073 getattr(self,'default_kwargs',None),
00074 self.tocall(),
00075 ]
00076 kwargs={}
00077 for D in dict_precedence:
00078 if D:
00079 if type(D)!=dict:
00080 raise ValueError, 'data must be specified as a dict!\n'\
00081 'Got: %s'%plotargs
00082 kwargs.update(D)
00083 return kwargs
00084
00085 def __repr__(self):
00086 return '<%s: %s, %s>'%(
00087 self.__class__.__name__,
00088 self.tocall,
00089 self.basename,
00090 )
00091
00092 def prefix(self,pre):
00093 """Generalized form of url/filename reporting.
00094
00095 pre: prefix to attach to the basename and suffix.
00096
00097 """
00098 di=dict(self.convert_to)
00099 return dict([(k,pre+self.basename+di[k]['suffix']) for k in di])
00100
00101 def get_urls(self):
00102 """Return dictionary of image URLs.
00103
00104 Make the plot if it doesn't exist or caching is off.
00105
00106 """
00107 files_dont_exist=sum([
00108 not os.path.exists(fn) for fn in self.get_filenames().values()])
00109 if not self.enable_caching or files_dont_exist:
00110 try:
00111 self.makefiles()
00112
00113
00114 except Exception, e:
00115 raise PlotError,'%s: %s when trying to make %s'%(
00116 e.__class__.__name__,e,self), sys.exc_traceback
00117 return self.prefix(settings.MEDIA_URL)
00118
00119 def makefiles(self):
00120 """Make initial file and conversions.
00121
00122 makefile() just makes the initial file.
00123
00124 """
00125 self.makedirs()
00126 self.makefile()
00127 self.convert()
00128 self.enable_caching=True
00129
00130 def makedirs(self):
00131 """Make subdirectories of media/ for this plot.
00132
00133 """
00134 try:
00135 D,f=os.path.split(self.from_filename())
00136 os.makedirs(D)
00137 except OSError:
00138 pass
00139
00140 def to_html(self):
00141 """Render the PNG image and link to the PDF.
00142
00143 """
00144 return self.render_html('to_html.html')
00145
00146 def to_html_thumb(self):
00147 """Render the PNG thumb.
00148
00149 """
00150 return self.render_html('to_html_thumb.html')
00151
00152 def to_html_nolink(self):
00153 """Render the PNG without link to PDF.
00154
00155 """
00156 return self.render_html('to_html_nolink.html')
00157
00158 def render_html(self,tfile):
00159 """Render a HTML template with self as context 'plot'.
00160
00161 """
00162 try:
00163
00164
00165
00166 old=self.enable_caching
00167 html=render_to_string('dataplot/'+tfile,{'plot':self})
00168
00169 self.enable_caching=old
00170 except PlotError, e:
00171 if settings.DEBUG:
00172 raise
00173 else:
00174 html=render_to_string("dataplot/error.html",
00175 {'e':e,'etype':e.__class__.__name__})
00176 return mark_safe(html)
00177
00178 def from_filename(self):
00179 return self.get_filenames()[self.convert_from]
00180
00181 def from_url(self):
00182 return self.get_urls()[self.convert_from]
00183
00184 def get_filenames(self):
00185 """Return dictionary of image filenames.
00186
00187 """
00188 return self.prefix(os.path.join(settings.MEDIA_ROOT,''))
00189
00190 def get_full_base(self):
00191 suffix=self.convert_to[self.convert_from]['suffix']
00192 return self.get_filenames()[self.convert_from][:-len(suffix)]
00193
00194 def convert(self):
00195 """Convert from PDF to other formats using ImageMagick.
00196
00197 """
00198 filenames=self.get_filenames()
00199 src=filenames.pop(self.convert_from)
00200 for k in filenames:
00201 cargs=self.convert_to[k].get('convert_args','')
00202 convert_cmd=' '.join([
00203 self.convert_binary,
00204 cargs,
00205 src,
00206 filenames[k],
00207 ])
00208 os.system(convert_cmd)
00209
00210 self.do_chgrp()
00211
00212 def do_chgrp(self):
00213 """Change group write perms if requested.
00214
00215 This is useful if your testing and production webservers have
00216 different users but share the same media directory.
00217
00218 """
00219 if self.chgrp:
00220 for fn in self.get_filenames().values():
00221 self.do_chgrp_on(fn)
00222
00223 def do_chgrp_on(self,fn):
00224 perm_cmd='chmod g+w %s ; chgrp %s %s'%(
00225 fn,self.chgrp,fn)
00226 os.system(perm_cmd)
00227
00228 def get_app_dirs(self):
00229 """Dig through settings.INSTALLED_APPS for full paths.
00230
00231 """
00232 return [os.path.dirname(
00233 __import__(mn,[],[],'.'.split(mn)[-1]).__file__)
00234 for mn in settings.INSTALLED_APPS]
00235
00236 def view(self):
00237 """Use some other program to look at rendered source image.
00238
00239 Will make the source image if it does not exist yet.
00240
00241 """
00242 filename=self.from_filename()
00243 if not os.path.exists(filename):
00244 self.makefile()
00245 cmd='%s %s'%(self.view_program,filename)
00246 os.system(cmd)