I needed to store arbitrary data in a relational database of multiple data-types so I came up with a solution of in addition to storing the data itself also to store what is the datatype of the data (str, int, etc). This allows upon retreival to cast the string which is stored in the db into whatever proper data-type the data is. In order to store the data-type I made a custom model field:
class DataType(object):
SUPPORTED_TYPES = {
u'unicode': unicode,
u'str': str,
u'bool': bool,
u'int': int,
u'float': float
}
INVERSE_SUPPORTED_TYPES = dict(zip(SUPPORTED_TYPES.values(), SUPPORTED_TYPES.keys()))
TYPE_CHOICES = dict(zip(SUPPORTED_TYPES.keys(), SUPPORTED_TYPES.keys()))
def __init__(self, datatype=None):
if not datatype:
datatype = unicode
t_datatype = type(datatype)
if t_datatype in [str, unicode]:
self.datatype = self.SUPPORTED_TYPES[datatype]
elif t_datatype is type and datatype in self.INVERSE_SUPPORTED_TYPES.keys():
self.datatype = datatype
elif t_datatype is DataType:
self.datatype = datatype.datatype
else:
raise TypeError('Unsupported %s' % str(t_datatype))
def __unicode__(self):
return self.INVERSE_SUPPORTED_TYPES[self.datatype]
def __str__(self):
return str(self.__unicode__())
def __len__(self):
return len(self.__unicode__())
def __call__(self, *args, **kwargs):
return self.datatype(*args, **kwargs)
class DataTypeField(models.CharField):
__metaclass__ = models.SubfieldBase
description = 'Field for storing python data-types in db with capability to get python the data-type back'
def __init__(self, **kwargs):
defaults = {}
overwrites = {
'max_length': 8
}
defaults.update(kwargs)
defaults.update(overwrites)
super(DataTypeField, self).__init__(**overwrites)
def to_python(self, value):
return DataType(value)
def get_prep_value(self, value):
return unicode(DataType(value))
def value_to_string(self, obj):
val = self._get_val_from_obj(obj)
return self.get_prep_value(val)
So this allows me to do something like that:
class FooModel(models.Model):
data = models.TextField()
data_type = DataTypeField()
>>> foo = FooModel.objects.create(data='17.94', data_type=float)
>>> foo.data_type(foo.data)
17.94
>>> type(foo.data_type(foo.data))
float
So my problem is that in the Django Admin (I am using ModelAdmin), the value for data_type in the textbox does not show up properly. Whenever it is float (and in db it is stores as float, I checked), the value displayed is 0.0. For int it displays 0. For bool it displays False. Instead of showing the string representation of the data_type, somewhere Django actually calls it, which means the __call__ is called withour an parameters, which results in those values. For example:
>>> DataType(float)()
0.0
>>> DataType(int)()
0
>>> DataType(bool)()
False
I figured out how to monkey patch it by replacing __call__ method with the following:
def __call__(self, *args, **kwargs):
if not args and not kwargs:
return self.__unicode__()
return self.datatype(*args, **kwargs)
This displays the correct value in the form however I feel that this is not very elegant. Is there any way to make it better? I could not figure out where Django called the field value in the first place.
Thanx
wrt why your DataType get called, read this: https://docs.djangoproject.com/en/1.4/topics/templates/#accessing-method-calls
The clean solution might be to simply rename call to something more explicit.