00001 """Auto-define and -update mechanism for django-dataplot images.
00002
00003 Comment out this line in your models.py:
00004 # from django.db import models
00005
00006 Then add:
00007 from dataplot import plotmodels as models
00008
00009 """
00010 from django.db.models.base import ModelBase
00011 from django.db.models import *
00012 from django.utils.functional import curry
00013 from copy import copy
00014 import dataplot
00015 import os,re
00016 import pdb
00017
00018 class DataplotImproperlyConfigured(dataplot.PlotError): pass
00019
00020 UNSAFE_FILE_CHARS=re.compile(r'[^a-zA-Z0-9]')
00021
00022 def call_if_possible(i):
00023 if callable(i):
00024 return i()
00025 else:
00026 return i
00027
00028 def get_plot_args(self,kwargs):
00029 """To be curried and used as a method for plotting.
00030
00031 """
00032 new_plot_dict=dict(kwargs['plot_dict'])
00033 qsattr=kwargs.get('qs','plotable')
00034 if isinstance(self,Model):
00035 m=getattr(self,qsattr)
00036 else:
00037 m=getattr(kwargs['toset'],qsattr,kwargs['toset'].all)
00038 qs=m()
00039 for k,v in new_plot_dict.iteritems():
00040 try:
00041 vals=[getattr(row,v) for row in qs]
00042 new=[call_if_possible(val) for val in vals]
00043 except Exception, e:
00044 try:
00045 attr=getattr(self,v,v)
00046 new=call_if_possible(attr)
00047 except:
00048 new=v
00049 new_plot_dict[k]=new
00050 try:
00051
00052 for k,v in kwargs['plot'].default_args_map.iteritems():
00053 new_plot_dict.setdefault(k,kwargs['plot_dict'][v])
00054 except:
00055 pass
00056 return new_plot_dict
00057
00058 class ModelBase(ModelBase):
00059 """Extend ModelBase to initialize dataplots.
00060
00061 """
00062
00063 def __init__(self,*posargs,**kwargs):
00064 """automatic dataplot construction based on DATAPLOTS syntax
00065
00066 """
00067 super(ModelBase,self).__init__(*posargs,**kwargs)
00068
00069
00070
00071
00072 self.objects=self._default_manager=copy(self.objects)
00073 self.objects.model=self
00074
00075
00076 toset_tups=[
00077 (self.objects,'MANAGER_DATAPLOTS'),
00078 (self,'DATAPLOTS'),
00079 ]
00080 for o,attr in toset_tups:
00081 for plotarg in getattr(self,attr,()):
00082 dp=Dataplot(plotarg,o)
00083 dp.defaults()
00084 dp.set_method()
00085 if attr!='DATAPLOTS':
00086
00087 dp.set_attribute()
00088
00089 class Model(Model):
00090 """Enables figure autosave with Django-dataplot.
00091
00092 All attributes of this model which are instances of
00093 dataplot.GenericPlot will be resaved.
00094
00095 """
00096 __metaclass__=ModelBase
00097
00098 def __init__(self,*args,**kwargs):
00099 super(Model,self).__init__(*args,**kwargs)
00100 for plotarg in getattr(self,'DATAPLOTS',()):
00101 dp=Dataplot(plotarg,self)
00102 dp.defaults()
00103 dp.set_attribute()
00104
00105 def save(self):
00106 """Save method which allows for maximum configurability.
00107
00108 On a model with no custom save method, we will call django's
00109 save first, then try to make plots for this object.
00110
00111 On a model with a custom save method, you should call
00112 make_plots and Model.save yourself, depending on when it is
00113 appropriate in terms of your data processing.
00114
00115 """
00116 super(Model,self).save()
00117 self.DATAPLOT_CHECK_AUTOSAVE=True
00118 self.save_plots()
00119
00120 def save_plots(self):
00121 force=not getattr(self,'DATAPLOT_CHECK_AUTOSAVE',False)
00122 self.make_model_plots(force)
00123 self.make_manager_plots(force)
00124
00125 def make_model_plots(self,force=True):
00126 """Try to remake plots related to this model.
00127
00128 """
00129 make_plots(self,force)
00130
00131 @classmethod
00132 def make_manager_plots(cls,force=True):
00133 """Try to remake plots related to this model's manager.
00134
00135 """
00136 make_plots(cls.objects,force)
00137
00138 def make_plots(x,force):
00139 """Look for dataplots in attributes, and remake them.
00140
00141 """
00142 for at in x.__dict__.values():
00143 if isinstance(at,dataplot.GenericPlot) and at.enable_caching and (at.autosave or force):
00144 at.makefiles()
00145
00146 class Dataplot(dict):
00147 """Parsing logic for dataplot autoconfig tuples.
00148
00149 This is just used for DRY convenience here and should not be used
00150 outside this module.
00151
00152 """
00153
00154 def __init__(self,plot,toset):
00155 """Initialize a dataplot using sensible defaults.
00156
00157 This can be a
00158
00159 dataplot (subclass of dataplot.GenericPlot, i.e. R.Scatter) in
00160 this case, we assume basename of scatter and a plot method
00161 called get_scatter_args.
00162
00163 tuple (dataplot,dict) where dict is a dictionary of kwargs
00164 used to initialize the dataplot. So you can use this form if
00165 you want to override the default basename, get_plot_args
00166 method, or other plot parameters.
00167
00168 """
00169 self.plot=plot
00170 if type(plot)==tuple:
00171 plot,kwargs=plot
00172 self.update(kwargs)
00173 self['plot']=plot
00174 self['toset']=toset
00175
00176 def defaults(self):
00177 """Derive sensible defaults from provided info.
00178
00179 """
00180 self.setdefault('plotname',self['plot'].__name__)
00181
00182 self.setdefault('attribute',self['plotname'])
00183
00184 get_plot_args=self.get('get_plot_args',self['attribute'])
00185
00186 if type(get_plot_args)==dict:
00187 self['plot_dict']=get_plot_args
00188 methodname=self['attribute']
00189 elif type(get_plot_args)==str:
00190 self['plot_dict']=None
00191 methodname=get_plot_args
00192 else:
00193 raise DataplotImproperlyConfigured(
00194 "get_plot_args must be unassigned, dict, or str")
00195 self['methodname']='%s_args'%methodname
00196
00197 def set_method(self):
00198 """Set data-gathering method if necessary.
00199
00200 """
00201 no_method=not hasattr(
00202 self['toset'].__class__,self['methodname'])
00203 if isinstance(self['toset'],ModelBase):
00204 toset=self['toset']
00205 else:
00206 toset=self['toset'].__class__
00207 if self['plot_dict'] and no_method:
00208 setattr(
00209 toset,
00210 self['methodname'],
00211 curry(get_plot_args,kwargs=self))
00212
00213 def set_attribute(self):
00214 """Set dataplot instance.
00215
00216 """
00217 if self['attribute']==self['plotname']:
00218 name=self['attribute']
00219 else:
00220 name='%s_%s'%(self['plotname'],self['attribute'])
00221 if isinstance(self['toset'],Model):
00222 name += '_id%s_%s'%(self['toset'].id,self['toset'])
00223 subdir=os.path.join(
00224 self['toset'].__module__.split('.')[-2],
00225 self['toset'].__class__.__name__)
00226 basename=subdir!=None and os.path.join(subdir,name) or name
00227 method=getattr(self['toset'],self['methodname'],None)
00228 if method:
00229 inst=self['plot'](
00230 basename,method,**self.setdefault('init_args',{}))
00231 else:
00232 raise DataplotImproperlyConfigured(
00233 "\nYou must define attribute "+
00234 self['methodname']+
00235 " for \n"+
00236 str(self['toset'])+
00237 "\nsince you gave\n"+
00238 str(self.plot))
00239 setattr(self['toset'],self['attribute'],inst)
00240