最 近需要和财务数据打交道,就得为数据库里存储数据选择一个好的储存方式。一开始是想使用 DecimalField 的,毕竟这是Django的原生字段。可是使用下来发现了两点问题,一是 DecimalField 在数据库中储存使用的是numeric的类型(我使用的是PostgreSQL),如果没记错的话,numeric在数据库中应该是使用的字符串来储存的,那么在使用求和之类的聚合时可能效率上会有问题;二是 DecimalField 返回的 Decimal 类型并不被CJSON或者SimpleJSON支持,需要手动转换,比较麻烦。
由于这两个问题,便萌生了自定义字段类型的想法。基本的思路是使用整型来储存,由于财务数据也就两位小数,把每个数据乘以100就能不失精度的储存。然后在获取的时候,在转换回来就可以了。看上去也不是什么复杂的工作,便立刻开始着手做了。
我的向来的习惯时,如果是自定义Django类型的话,首先搬出Django源码看看。看了一下 PositiveIntegerField 的实现,觉得也就是在 to_python() 和 get_db_prep_value() 上下点文章罢了,于是就出了下面一段代码:
class MoneyField(models.IntegerField): def get_db_prep_value(self, value): if value is None: return None return int(value * 100) def to_python(self, value): if value is None or isinstance(value, float): return value try: return float(value) / 100 except (TypeError, ValueError): raise exceptions.ValidationError( "This value must be an integer or a string represents an integer.")
在shell里面试了一下,查询和储存都没有问题,但是在获取的时候,返回值并没有像我想像的那样转换回浮点。于是继续看源码,可是看了大半天也没弄出什么名堂出来。暂时没有什么办法的情况下,只好直接使用 IntegerField ,然后给模型增加一个getter和setter property将就了。
今天无意中翻Django的文档发现,如果希望 to_python 起效,必须把这个字段类型的 __metaclass 设置为 SubfieldBase 的值才行。尝试以后果然起效,最后再为 ModelForm 把表单类型重新定义一下,基本完工了。
class MoneyField(models.IntegerField): ''' It is supposed the aggregation on integer is fast than numeric type in database duo to how they are stored. As money only have 2 decimal place, it can be converted to an Integer despite of its decimal point. The python class decimal.Decimal also has a shortcoming that it is not json serializable. This money field appears as a float number to the front end, so it does not meet the headache as DecimalField. ''' __metaclass__ = models.SubfieldBase # very important for to_python def get_db_prep_value(self, value): if value is None: return None from decimal import Decimal return int(Decimal(str(value)) * 100) def to_python(self, value): if value is None or isinstance(value, float): return value from decimal import Decimal try: return float(Decimal(value) / 100) except (TypeError, ValueError): raise exceptions.ValidationError( "This value must be an integer or a string represents an integer.") def formfield(self, **kwargs): from django.forms import FloatField defaults = {'form_class': FloatField} defaults.update(kwargs) return super(MoneyField, self).formfield(**defaults)
不过现在的代码还有一个问题,当使用了 values() 、 values_list() 、 only() 或 defer() 这样的部分选取数值的查询方法时, to_python() 仍然不会被调用。可是我试过了,拿 django.db.models.DateField 为例,这样查询时这样字段的值还是会转换为 datetime.date 类型的。不过暂时我也找不到其他的办法了,先将就用着。出现这样的调用时手动转换一下吧,直到俺找到解决办法…… 各位路过打酱油的童鞋如果知道的话也麻烦留言告诉我一下:)


嗯,最近也想用django弄个股票类的网站,也遇到用了values_list后弄出的日期数据是datetime.date类型的,不知道该怎样解决,博主是用什么方法解决的呢?