DecimalField now stores as float not string (#289)
This commit is contained in:
parent
ac6e793bbe
commit
5e94637adc
@ -54,33 +54,33 @@ Querying
|
|||||||
Fields
|
Fields
|
||||||
======
|
======
|
||||||
|
|
||||||
.. autoclass:: mongoengine.StringField
|
.. autoclass:: mongoengine.fields.StringField
|
||||||
.. autoclass:: mongoengine.URLField
|
.. autoclass:: mongoengine.fields.URLField
|
||||||
.. autoclass:: mongoengine.EmailField
|
.. autoclass:: mongoengine.fields.EmailField
|
||||||
.. autoclass:: mongoengine.IntField
|
.. autoclass:: mongoengine.fields.IntField
|
||||||
.. autoclass:: mongoengine.LongField
|
.. autoclass:: mongoengine.fields.LongField
|
||||||
.. autoclass:: mongoengine.FloatField
|
.. autoclass:: mongoengine.fields.FloatField
|
||||||
.. autoclass:: mongoengine.DecimalField
|
.. autoclass:: mongoengine.fields.DecimalField
|
||||||
.. autoclass:: mongoengine.BooleanField
|
.. autoclass:: mongoengine.fields.BooleanField
|
||||||
.. autoclass:: mongoengine.DateTimeField
|
.. autoclass:: mongoengine.fields.DateTimeField
|
||||||
.. autoclass:: mongoengine.ComplexDateTimeField
|
.. autoclass:: mongoengine.fields.ComplexDateTimeField
|
||||||
.. autoclass:: mongoengine.EmbeddedDocumentField
|
.. autoclass:: mongoengine.fields.EmbeddedDocumentField
|
||||||
.. autoclass:: mongoengine.GenericEmbeddedDocumentField
|
.. autoclass:: mongoengine.fields.GenericEmbeddedDocumentField
|
||||||
.. autoclass:: mongoengine.DynamicField
|
.. autoclass:: mongoengine.fields.DynamicField
|
||||||
.. autoclass:: mongoengine.ListField
|
.. autoclass:: mongoengine.fields.ListField
|
||||||
.. autoclass:: mongoengine.SortedListField
|
.. autoclass:: mongoengine.fields.SortedListField
|
||||||
.. autoclass:: mongoengine.DictField
|
.. autoclass:: mongoengine.fields.DictField
|
||||||
.. autoclass:: mongoengine.MapField
|
.. autoclass:: mongoengine.fields.MapField
|
||||||
.. autoclass:: mongoengine.ReferenceField
|
.. autoclass:: mongoengine.fields.ReferenceField
|
||||||
.. autoclass:: mongoengine.GenericReferenceField
|
.. autoclass:: mongoengine.fields.GenericReferenceField
|
||||||
.. autoclass:: mongoengine.BinaryField
|
.. autoclass:: mongoengine.fields.BinaryField
|
||||||
.. autoclass:: mongoengine.FileField
|
.. autoclass:: mongoengine.fields.FileField
|
||||||
.. autoclass:: mongoengine.ImageField
|
.. autoclass:: mongoengine.fields.ImageField
|
||||||
.. autoclass:: mongoengine.GeoPointField
|
.. autoclass:: mongoengine.fields.GeoPointField
|
||||||
.. autoclass:: mongoengine.SequenceField
|
.. autoclass:: mongoengine.fields.SequenceField
|
||||||
.. autoclass:: mongoengine.ObjectIdField
|
.. autoclass:: mongoengine.fields.ObjectIdField
|
||||||
.. autoclass:: mongoengine.UUIDField
|
.. autoclass:: mongoengine.fields.UUIDField
|
||||||
.. autoclass:: mongoengine.GridFSError
|
.. autoclass:: mongoengine.fields.GridFSError
|
||||||
.. autoclass:: mongoengine.GridFSProxy
|
.. autoclass:: mongoengine.fields.GridFSProxy
|
||||||
.. autoclass:: mongoengine.ImageGridFsProxy
|
.. autoclass:: mongoengine.fields.ImageGridFsProxy
|
||||||
.. autoclass:: mongoengine.ImproperlyConfigured
|
.. autoclass:: mongoengine.fields.ImproperlyConfigured
|
||||||
|
@ -4,6 +4,7 @@ Changelog
|
|||||||
|
|
||||||
Changes in 0.8.X
|
Changes in 0.8.X
|
||||||
================
|
================
|
||||||
|
- DecimalField now stores as float not string (#289)
|
||||||
- UUIDField now stores as a binary by default (#292)
|
- UUIDField now stores as a binary by default (#292)
|
||||||
- Added Custom User Model for Django 1.5 (#285)
|
- Added Custom User Model for Django 1.5 (#285)
|
||||||
- Cascading saves now default to off (#291)
|
- Cascading saves now default to off (#291)
|
||||||
|
@ -173,8 +173,8 @@ latex_paper_size = 'a4'
|
|||||||
# Grouping the document tree into LaTeX files. List of tuples
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
('index', 'MongoEngine.tex', u'MongoEngine Documentation',
|
('index', 'MongoEngine.tex', 'MongoEngine Documentation',
|
||||||
u'Harry Marr', 'manual'),
|
'Ross Lawley', 'manual'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
@ -193,3 +193,6 @@ latex_documents = [
|
|||||||
|
|
||||||
# If false, no module index is generated.
|
# If false, no module index is generated.
|
||||||
#latex_use_modindex = True
|
#latex_use_modindex = True
|
||||||
|
|
||||||
|
autoclass_content = 'both'
|
||||||
|
|
||||||
|
@ -145,6 +145,35 @@ eg::
|
|||||||
a._mark_as_dirty('uuid')
|
a._mark_as_dirty('uuid')
|
||||||
a.save()
|
a.save()
|
||||||
|
|
||||||
|
DecimalField
|
||||||
|
------------
|
||||||
|
|
||||||
|
DecimalField now store floats - previous it was storing strings and that
|
||||||
|
made it impossible to do comparisons when querying correctly.::
|
||||||
|
|
||||||
|
# Old code
|
||||||
|
class Person(Document):
|
||||||
|
balance = DecimalField()
|
||||||
|
|
||||||
|
# New code
|
||||||
|
class Person(Document):
|
||||||
|
balance = DecimalField(force_string=True)
|
||||||
|
|
||||||
|
To migrate all the uuid's you need to touch each object and mark it as dirty
|
||||||
|
eg::
|
||||||
|
|
||||||
|
# Doc definition
|
||||||
|
class Person(Document):
|
||||||
|
balance = DecimalField()
|
||||||
|
|
||||||
|
# Mark all ReferenceFields as dirty and save
|
||||||
|
for p in Person.objects:
|
||||||
|
p._mark_as_dirty('balance')
|
||||||
|
p.save()
|
||||||
|
|
||||||
|
.. note:: DecimalField's have also been improved with the addition of precision
|
||||||
|
and rounding. See :class:`~mongoengine.DecimalField` for more information.
|
||||||
|
|
||||||
Cascading Saves
|
Cascading Saves
|
||||||
---------------
|
---------------
|
||||||
To improve performance document saves will no longer automatically cascade.
|
To improve performance document saves will no longer automatically cascade.
|
||||||
|
@ -260,30 +260,57 @@ class FloatField(BaseField):
|
|||||||
class DecimalField(BaseField):
|
class DecimalField(BaseField):
|
||||||
"""A fixed-point decimal number field.
|
"""A fixed-point decimal number field.
|
||||||
|
|
||||||
|
.. versionchanged:: 0.8
|
||||||
.. versionadded:: 0.3
|
.. versionadded:: 0.3
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, min_value=None, max_value=None, **kwargs):
|
def __init__(self, min_value=None, max_value=None, force_string=False,
|
||||||
self.min_value, self.max_value = min_value, max_value
|
precision=2, rounding=decimal.ROUND_HALF_UP, **kwargs):
|
||||||
|
"""
|
||||||
|
:param min_value: Validation rule for the minimum acceptable value.
|
||||||
|
:param max_value: Validation rule for the maximum acceptable value.
|
||||||
|
:param force_string: Store as a string.
|
||||||
|
:param precision: Number of decimal places to store.
|
||||||
|
:param rounding: The rounding rule from the python decimal libary:
|
||||||
|
|
||||||
|
- decimial.ROUND_CEILING (towards Infinity)
|
||||||
|
- decimial.ROUND_DOWN (towards zero)
|
||||||
|
- decimial.ROUND_FLOOR (towards -Infinity)
|
||||||
|
- decimial.ROUND_HALF_DOWN (to nearest with ties going towards zero)
|
||||||
|
- decimial.ROUND_HALF_EVEN (to nearest with ties going to nearest even integer)
|
||||||
|
- decimial.ROUND_HALF_UP (to nearest with ties going away from zero)
|
||||||
|
- decimial.ROUND_UP (away from zero)
|
||||||
|
- decimial.ROUND_05UP (away from zero if last digit after rounding towards zero would have been 0 or 5; otherwise towards zero)
|
||||||
|
|
||||||
|
Defaults to: ``decimal.ROUND_HALF_UP``
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.min_value = min_value
|
||||||
|
self.max_value = max_value
|
||||||
|
self.force_string = force_string
|
||||||
|
self.precision = decimal.Decimal(".%s" % ("0" * precision))
|
||||||
|
self.rounding = rounding
|
||||||
|
|
||||||
super(DecimalField, self).__init__(**kwargs)
|
super(DecimalField, self).__init__(**kwargs)
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
original_value = value
|
if value is None:
|
||||||
if not isinstance(value, basestring):
|
|
||||||
value = unicode(value)
|
|
||||||
try:
|
|
||||||
value = decimal.Decimal(value)
|
|
||||||
except ValueError:
|
|
||||||
return original_value
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
return decimal.Decimal(value).quantize(self.precision,
|
||||||
|
rounding=self.rounding)
|
||||||
|
|
||||||
def to_mongo(self, value):
|
def to_mongo(self, value):
|
||||||
|
if value is None:
|
||||||
|
return value
|
||||||
|
if self.force_string:
|
||||||
return unicode(value)
|
return unicode(value)
|
||||||
|
return float(self.to_python(value))
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
if not isinstance(value, decimal.Decimal):
|
if not isinstance(value, decimal.Decimal):
|
||||||
if not isinstance(value, basestring):
|
if not isinstance(value, basestring):
|
||||||
value = str(value)
|
value = unicode(value)
|
||||||
try:
|
try:
|
||||||
value = decimal.Decimal(value)
|
value = decimal.Decimal(value)
|
||||||
except Exception, exc:
|
except Exception, exc:
|
||||||
@ -295,6 +322,9 @@ class DecimalField(BaseField):
|
|||||||
if self.max_value is not None and value > self.max_value:
|
if self.max_value is not None and value > self.max_value:
|
||||||
self.error('Decimal value is too large')
|
self.error('Decimal value is too large')
|
||||||
|
|
||||||
|
def prepare_query_value(self, op, value):
|
||||||
|
return self.to_mongo(value)
|
||||||
|
|
||||||
|
|
||||||
class BooleanField(BaseField):
|
class BooleanField(BaseField):
|
||||||
"""A boolean field type.
|
"""A boolean field type.
|
||||||
|
@ -272,10 +272,8 @@ class FieldTest(unittest.TestCase):
|
|||||||
|
|
||||||
Person.drop_collection()
|
Person.drop_collection()
|
||||||
|
|
||||||
person = Person()
|
Person(height=Decimal('1.89')).save()
|
||||||
person.height = Decimal('1.89')
|
person = Person.objects.first()
|
||||||
person.save()
|
|
||||||
person.reload()
|
|
||||||
self.assertEqual(person.height, Decimal('1.89'))
|
self.assertEqual(person.height, Decimal('1.89'))
|
||||||
|
|
||||||
person.height = '2.0'
|
person.height = '2.0'
|
||||||
@ -289,6 +287,45 @@ class FieldTest(unittest.TestCase):
|
|||||||
|
|
||||||
Person.drop_collection()
|
Person.drop_collection()
|
||||||
|
|
||||||
|
def test_decimal_comparison(self):
|
||||||
|
|
||||||
|
class Person(Document):
|
||||||
|
money = DecimalField()
|
||||||
|
|
||||||
|
Person.drop_collection()
|
||||||
|
|
||||||
|
Person(money=6).save()
|
||||||
|
Person(money=8).save()
|
||||||
|
Person(money=10).save()
|
||||||
|
|
||||||
|
self.assertEqual(2, Person.objects(money__gt=Decimal("7")).count())
|
||||||
|
self.assertEqual(2, Person.objects(money__gt=7).count())
|
||||||
|
self.assertEqual(2, Person.objects(money__gt="7").count())
|
||||||
|
|
||||||
|
def test_decimal_storage(self):
|
||||||
|
class Person(Document):
|
||||||
|
btc = DecimalField(precision=4)
|
||||||
|
|
||||||
|
Person.drop_collection()
|
||||||
|
Person(btc=10).save()
|
||||||
|
Person(btc=10.1).save()
|
||||||
|
Person(btc=10.11).save()
|
||||||
|
Person(btc="10.111").save()
|
||||||
|
Person(btc=Decimal("10.1111")).save()
|
||||||
|
Person(btc=Decimal("10.11111")).save()
|
||||||
|
|
||||||
|
# How its stored
|
||||||
|
expected = [{'btc': 10.0}, {'btc': 10.1}, {'btc': 10.11},
|
||||||
|
{'btc': 10.111}, {'btc': 10.1111}, {'btc': 10.1111}]
|
||||||
|
actual = list(Person.objects.exclude('id').as_pymongo())
|
||||||
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
|
# How it comes out locally
|
||||||
|
expected = [Decimal('10.0000'), Decimal('10.1000'), Decimal('10.1100'),
|
||||||
|
Decimal('10.1110'), Decimal('10.1111'), Decimal('10.1111')]
|
||||||
|
actual = list(Person.objects().scalar('btc'))
|
||||||
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
def test_boolean_validation(self):
|
def test_boolean_validation(self):
|
||||||
"""Ensure that invalid values cannot be assigned to boolean fields.
|
"""Ensure that invalid values cannot be assigned to boolean fields.
|
||||||
"""
|
"""
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from convert_to_new_inheritance_model import *
|
from convert_to_new_inheritance_model import *
|
||||||
|
from decimalfield_as_float import *
|
||||||
from refrencefield_dbref_to_object_id import *
|
from refrencefield_dbref_to_object_id import *
|
||||||
from turn_off_inheritance import *
|
from turn_off_inheritance import *
|
||||||
from uuidfield_to_binary import *
|
from uuidfield_to_binary import *
|
||||||
|
50
tests/migration/decimalfield_as_float.py
Normal file
50
tests/migration/decimalfield_as_float.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import unittest
|
||||||
|
import decimal
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from mongoengine import Document, connect
|
||||||
|
from mongoengine.connection import get_db
|
||||||
|
from mongoengine.fields import StringField, DecimalField, ListField
|
||||||
|
|
||||||
|
__all__ = ('ConvertDecimalField', )
|
||||||
|
|
||||||
|
|
||||||
|
class ConvertDecimalField(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
connect(db='mongoenginetest')
|
||||||
|
self.db = get_db()
|
||||||
|
|
||||||
|
def test_how_to_convert_decimal_fields(self):
|
||||||
|
"""Demonstrates migrating from 0.7 to 0.8
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 1. Old definition - using dbrefs
|
||||||
|
class Person(Document):
|
||||||
|
name = StringField()
|
||||||
|
money = DecimalField(force_string=True)
|
||||||
|
monies = ListField(DecimalField(force_string=True))
|
||||||
|
|
||||||
|
Person.drop_collection()
|
||||||
|
Person(name="Wilson Jr", money=Decimal("2.50"),
|
||||||
|
monies=[Decimal("2.10"), Decimal("5.00")]).save()
|
||||||
|
|
||||||
|
# 2. Start the migration by changing the schema
|
||||||
|
# Change DecimalField - add precision and rounding settings
|
||||||
|
class Person(Document):
|
||||||
|
name = StringField()
|
||||||
|
money = DecimalField(precision=2, rounding=decimal.ROUND_HALF_UP)
|
||||||
|
monies = ListField(DecimalField(precision=2,
|
||||||
|
rounding=decimal.ROUND_HALF_UP))
|
||||||
|
|
||||||
|
# 3. Loop all the objects and mark parent as changed
|
||||||
|
for p in Person.objects:
|
||||||
|
p._mark_as_changed('money')
|
||||||
|
p._mark_as_changed('monies')
|
||||||
|
p.save()
|
||||||
|
|
||||||
|
# 4. Confirmation of the fix!
|
||||||
|
wilson = Person.objects(name="Wilson Jr").as_pymongo()[0]
|
||||||
|
self.assertTrue(isinstance(wilson['money'], float))
|
||||||
|
self.assertTrue(all([isinstance(m, float) for m in wilson['monies']]))
|
@ -3262,9 +3262,9 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
self.assertTrue(isinstance(results[0], dict))
|
self.assertTrue(isinstance(results[0], dict))
|
||||||
self.assertTrue(isinstance(results[1], dict))
|
self.assertTrue(isinstance(results[1], dict))
|
||||||
self.assertEqual(results[0]['name'], 'Bob Dole')
|
self.assertEqual(results[0]['name'], 'Bob Dole')
|
||||||
self.assertEqual(results[0]['price'], '1.11')
|
self.assertEqual(results[0]['price'], 1.11)
|
||||||
self.assertEqual(results[1]['name'], 'Barack Obama')
|
self.assertEqual(results[1]['name'], 'Barack Obama')
|
||||||
self.assertEqual(results[1]['price'], '2.22')
|
self.assertEqual(results[1]['price'], 2.22)
|
||||||
|
|
||||||
# Test coerce_types
|
# Test coerce_types
|
||||||
users = User.objects.only('name', 'price').as_pymongo(coerce_types=True)
|
users = User.objects.only('name', 'price').as_pymongo(coerce_types=True)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user