"""Auto-define and -update mechanism for django-dataplot images.
Comment out this line in your models.py:
# from django.db import models
Then add:
from dataplot import plotmodels as models
"""
from django.db.models.base import ModelBase
from django.db.models import * # so we can use this as a models file
from django.utils.functional import curry
from copy import copy
import dataplot
import os,re
import pdb
class DataplotImproperlyConfigured(dataplot.PlotError): pass
UNSAFE_FILE_CHARS=re.compile(r'[^a-zA-Z0-9]')
def call_if_possible(i):
if callable(i):
return i()
else:
return i
def get_plot_args(self,kwargs):
"""To be curried and used as a method for plotting.
"""
new_plot_dict=dict(kwargs['plot_dict'])
qsattr=kwargs.get('qs','plotable')
if isinstance(self,Model):
m=getattr(self,qsattr)
else:
m=getattr(kwargs['toset'],qsattr,kwargs['toset'].all)
qs=m()
for k,v in new_plot_dict.iteritems():
try: # to get value list from fields
vals=[getattr(row,v) for row in qs]
new=[call_if_possible(val) for val in vals]
except Exception, e: # field not specified
try: # to get a named attribute
attr=getattr(self,v,v)
new=call_if_possible(attr)
except: # not a named attribute, just keep the value
new=v
new_plot_dict[k]=new
try:
# set default args if specified
for k,v in kwargs['plot'].default_args_map.iteritems():
new_plot_dict.setdefault(k,kwargs['plot_dict'][v])
except: # no problem if no default args
pass
return new_plot_dict
class ModelBase(ModelBase):
"""Extend ModelBase to initialize dataplots.
"""
def __init__(self,*posargs,**kwargs):
"""automatic dataplot construction based on DATAPLOTS syntax
"""
super(ModelBase,self).__init__(*posargs,**kwargs)
#print self
# With django queryset-refactor branch integrated into main trunk,
# dataplot.plotsmodels.Model will have no objects attribute,
# so just do nothing in that case
if 'objects' not in dir(self):
return
#print self,self.objects.model._meta.db_table,self._meta.db_table
#print self.objects,self.ChangeManipulator.manager.model._meta.db_table,self._default_manager.model._meta.db_table
toset_tups=[
(self.objects,'MANAGER_DATAPLOTS'),
(self,'DATAPLOTS'), # self is the model subclass
]
for o,attr in toset_tups:
for plotarg in getattr(self,attr,()):
dp=Dataplot(plotarg,o)
dp.defaults()
dp.set_method()
if attr!='DATAPLOTS': # only set plot for manager
# will set plot for model at instantiation
dp.set_attribute()
class Model(Model):
"""Enables figure autosave with Django-dataplot.
All attributes of this model which are instances of
dataplot.GenericPlot will be resaved.
"""
__metaclass__=ModelBase
class Meta:
abstract=True
def __init__(self,*args,**kwargs):
super(Model,self).__init__(*args,**kwargs)
for plotarg in getattr(self,'DATAPLOTS',()):
dp=Dataplot(plotarg,self) # self is the instance
dp.defaults()
dp.set_attribute()
def save(self,*args,**kwargs):
"""Save method which allows for maximum configurability.
On a model with no custom save method, we will call django's
save first, then try to make plots for this object.
On a model with a custom save method, you should call
make_plots and Model.save yourself, depending on when it is
appropriate in terms of your data processing.
"""
super(Model,self).save(*args,**kwargs)
self.DATAPLOT_CHECK_AUTOSAVE=True
self.save_plots()
def save_plots(self):
force=not getattr(self,'DATAPLOT_CHECK_AUTOSAVE',False)
self.make_model_plots(force)
self.make_manager_plots(force)
def make_model_plots(self,force=True):
"""Try to remake plots related to this model.
"""
make_plots(self,force)
@classmethod
def make_manager_plots(cls,force=True):
"""Try to remake plots related to this model's manager.
"""
make_plots(cls.objects,force)
def make_plots(x,force):
"""Look for dataplots in attributes, and remake them.
"""
for at in x.__dict__.values():
if isinstance(at,dataplot.GenericPlot) and at.enable_caching and (at.autosave or force):
at.makefiles()
class Dataplot(dict):
"""Parsing logic for dataplot autoconfig tuples.
This is just used for DRY convenience here and should not be used
outside this module.
FIXME: Change to new self.DATAPLOTS.add syntax.
"""
def __init__(self,plot,toset):
"""Initialize a dataplot using sensible defaults.
This can be a
dataplot (subclass of dataplot.GenericPlot, i.e. R.Scatter) in
this case, we assume basename of scatter and a plot method
called get_scatter_args.
tuple (dataplot,dict) where dict is a dictionary of kwargs
used to initialize the dataplot. So you can use this form if
you want to override the default basename, get_plot_args
method, or other plot parameters.
"""
self.plot=plot
if type(plot)==tuple:
plot,kwargs=plot
self.update(kwargs)
self['plot']=plot
self['toset']=toset
def defaults(self):
"""Derive sensible defaults from provided info.
"""
self.setdefault('plotname',self['plot'].__name__)
# attribute name is by default the plot name
self.setdefault('attribute',self['plotname'])
# get_plot_args is by default the attribute name
get_plot_args=self.get('get_plot_args',self['attribute'])
#pdb.set_trace()
if type(get_plot_args)==dict:
self['plot_dict']=get_plot_args
methodname=self['attribute']
elif type(get_plot_args)==str:
self['plot_dict']=None
methodname=get_plot_args
else:
raise DataplotImproperlyConfigured(
"get_plot_args must be unassigned, dict, or str")
self['methodname']='%s_args'%methodname
def set_method(self):
"""Set data-gathering method if necessary.
"""
no_method=not hasattr(
self['toset'].__class__,self['methodname'])
if isinstance(self['toset'],ModelBase):
toset=self['toset']
else:
toset=self['toset'].__class__
if self['plot_dict'] and no_method:
setattr(
toset,
self['methodname'],
curry(get_plot_args,kwargs=self))
def set_attribute(self):
"""Set dataplot instance.
"""
if self['attribute']==self['plotname']:
name=self['attribute']
else:
name='%s_%s'%(self['plotname'],self['attribute'])
# append id and string rep if this is a DB object
if isinstance(self['toset'],Model):
name += '_id%s_%s'%(self['toset'].id,self['toset'])
desc=self.get('description',None)
if desc:
name += '_' + desc
subdir=os.path.join(
self['toset'].__module__.split('.')[-2],
self['toset'].__class__.__name__)
basename=subdir!=None and os.path.join(subdir,name) or name
method=getattr(self['toset'],self['methodname'],None)
if method:
inst=self['plot'](
basename,method,**self.setdefault('init_args',{}))
else:
raise DataplotImproperlyConfigured(
"\nYou must define attribute "+
self['methodname']+
" for \n"+
str(self['toset'])+
"\nsince you gave\n"+
str(self.plot))
setattr(self['toset'],self['attribute'],inst)