Added json serialisation support
- Added to_json and from_json to Document (MongoEngine/mongoengine#1) - Added to_json and from_json to QuerySet (MongoEngine/mongoengine#131)
This commit is contained in:
parent
4b45c0cd14
commit
b8d53a6f0d
@ -4,7 +4,9 @@ Changelog
|
|||||||
|
|
||||||
Changes in 0.8
|
Changes in 0.8
|
||||||
==============
|
==============
|
||||||
- Updated index creation now tied to Document class ((MongoEngine/mongoengine#102)
|
- Added to_json and from_json to Document (MongoEngine/mongoengine#1)
|
||||||
|
- Added to_json and from_json to QuerySet (MongoEngine/mongoengine#131)
|
||||||
|
- Updated index creation now tied to Document class (MongoEngine/mongoengine#102)
|
||||||
- Added none() to queryset (MongoEngine/mongoengine#127)
|
- Added none() to queryset (MongoEngine/mongoengine#127)
|
||||||
- Updated SequenceFields to allow post processing of the calculated counter value (MongoEngine/mongoengine#141)
|
- Updated SequenceFields to allow post processing of the calculated counter value (MongoEngine/mongoengine#141)
|
||||||
- Added clean method to documents for pre validation data cleaning (MongoEngine/mongoengine#60)
|
- Added clean method to documents for pre validation data cleaning (MongoEngine/mongoengine#60)
|
||||||
|
@ -2,6 +2,7 @@ import operator
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
import pymongo
|
import pymongo
|
||||||
|
from bson import json_util
|
||||||
from bson.dbref import DBRef
|
from bson.dbref import DBRef
|
||||||
|
|
||||||
from mongoengine import signals
|
from mongoengine import signals
|
||||||
@ -253,6 +254,15 @@ class BaseDocument(object):
|
|||||||
if errors:
|
if errors:
|
||||||
raise ValidationError('ValidationError', errors=errors)
|
raise ValidationError('ValidationError', errors=errors)
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
"""Converts a document to JSON"""
|
||||||
|
return json_util.dumps(self.to_mongo())
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_json(cls, json_data):
|
||||||
|
"""Converts json data to an unsaved document instance"""
|
||||||
|
return cls._from_son(json_util.loads(json_data))
|
||||||
|
|
||||||
def __expand_dynamic_values(self, name, value):
|
def __expand_dynamic_values(self, name, value):
|
||||||
"""expand any dynamic values to their correct types / values"""
|
"""expand any dynamic values to their correct types / values"""
|
||||||
if not isinstance(value, (dict, list, tuple)):
|
if not isinstance(value, (dict, list, tuple)):
|
||||||
|
@ -6,6 +6,7 @@ import re
|
|||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from bson.code import Code
|
from bson.code import Code
|
||||||
|
from bson import json_util
|
||||||
import pymongo
|
import pymongo
|
||||||
from pymongo.common import validate_read_preference
|
from pymongo.common import validate_read_preference
|
||||||
|
|
||||||
@ -1216,6 +1217,15 @@ class QuerySet(object):
|
|||||||
max_depth += 1
|
max_depth += 1
|
||||||
return self._dereference(self, max_depth=max_depth)
|
return self._dereference(self, max_depth=max_depth)
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
"""Converts a queryset to JSON"""
|
||||||
|
return json_util.dumps(self._collection_obj.find(self._query))
|
||||||
|
|
||||||
|
def from_json(self, json_data):
|
||||||
|
"""Converts json data to unsaved objects"""
|
||||||
|
son_data = json_util.loads(json_data)
|
||||||
|
return [self._document._from_son(data) for data in son_data]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _dereference(self):
|
def _dereference(self):
|
||||||
if not self.__dereference:
|
if not self.__dereference:
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
# TODO EXPLICT IMPORTS
|
import sys
|
||||||
|
sys.path[0:0] = [""]
|
||||||
|
import unittest
|
||||||
|
|
||||||
from class_methods import *
|
from class_methods import *
|
||||||
from delta import *
|
from delta import *
|
||||||
@ -6,6 +8,7 @@ from dynamic import *
|
|||||||
from indexes import *
|
from indexes import *
|
||||||
from inheritance import *
|
from inheritance import *
|
||||||
from instance import *
|
from instance import *
|
||||||
|
from json_serialisation import *
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -346,7 +346,7 @@ class InstanceTest(unittest.TestCase):
|
|||||||
meta = {'shard_key': ('superphylum',)}
|
meta = {'shard_key': ('superphylum',)}
|
||||||
|
|
||||||
Animal.drop_collection()
|
Animal.drop_collection()
|
||||||
doc = Animal(superphylum = 'Deuterostomia')
|
doc = Animal(superphylum='Deuterostomia')
|
||||||
doc.save()
|
doc.save()
|
||||||
doc.reload()
|
doc.reload()
|
||||||
Animal.drop_collection()
|
Animal.drop_collection()
|
||||||
|
81
tests/document/json_serialisation.py
Normal file
81
tests/document/json_serialisation.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import sys
|
||||||
|
sys.path[0:0] = [""]
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from nose.plugins.skip import SkipTest
|
||||||
|
from datetime import datetime
|
||||||
|
from bson import ObjectId
|
||||||
|
|
||||||
|
import pymongo
|
||||||
|
|
||||||
|
from mongoengine import *
|
||||||
|
|
||||||
|
__all__ = ("TestJson",)
|
||||||
|
|
||||||
|
|
||||||
|
class TestJson(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
connect(db='mongoenginetest')
|
||||||
|
|
||||||
|
def test_json_simple(self):
|
||||||
|
|
||||||
|
class Embedded(EmbeddedDocument):
|
||||||
|
string = StringField()
|
||||||
|
|
||||||
|
class Doc(Document):
|
||||||
|
string = StringField()
|
||||||
|
embedded_field = EmbeddedDocumentField(Embedded)
|
||||||
|
|
||||||
|
doc = Doc(string="Hi", embedded_field=Embedded(string="Hi"))
|
||||||
|
|
||||||
|
self.assertEqual(doc, Doc.from_json(doc.to_json()))
|
||||||
|
|
||||||
|
def test_json_complex(self):
|
||||||
|
|
||||||
|
if pymongo.version_tuple[0] <= 2 and pymongo.version_tuple[1] <= 3:
|
||||||
|
raise SkipTest("Need pymongo 2.4 as has a fix for DBRefs")
|
||||||
|
|
||||||
|
class EmbeddedDoc(EmbeddedDocument):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Simple(Document):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Doc(Document):
|
||||||
|
string_field = StringField(default='1')
|
||||||
|
int_field = IntField(default=1)
|
||||||
|
float_field = FloatField(default=1.1)
|
||||||
|
boolean_field = BooleanField(default=True)
|
||||||
|
datetime_field = DateTimeField(default=datetime.now)
|
||||||
|
embedded_document_field = EmbeddedDocumentField(EmbeddedDoc,
|
||||||
|
default=lambda: EmbeddedDoc())
|
||||||
|
list_field = ListField(default=lambda: [1, 2, 3])
|
||||||
|
dict_field = DictField(default=lambda: {"hello": "world"})
|
||||||
|
objectid_field = ObjectIdField(default=ObjectId)
|
||||||
|
reference_field = ReferenceField(Simple, default=lambda:
|
||||||
|
Simple().save())
|
||||||
|
map_field = MapField(IntField(), default=lambda: {"simple": 1})
|
||||||
|
decimal_field = DecimalField(default=1.0)
|
||||||
|
complex_datetime_field = ComplexDateTimeField(default=datetime.now)
|
||||||
|
url_field = URLField(default="http://mongoengine.org")
|
||||||
|
dynamic_field = DynamicField(default=1)
|
||||||
|
generic_reference_field = GenericReferenceField(
|
||||||
|
default=lambda: Simple().save())
|
||||||
|
sorted_list_field = SortedListField(IntField(),
|
||||||
|
default=lambda: [1, 2, 3])
|
||||||
|
email_field = EmailField(default="ross@example.com")
|
||||||
|
geo_point_field = GeoPointField(default=lambda: [1, 2])
|
||||||
|
sequence_field = SequenceField()
|
||||||
|
uuid_field = UUIDField(default=uuid.uuid4)
|
||||||
|
generic_embedded_document_field = GenericEmbeddedDocumentField(
|
||||||
|
default=lambda: EmbeddedDoc())
|
||||||
|
|
||||||
|
doc = Doc()
|
||||||
|
self.assertEqual(doc, Doc.from_json(doc.to_json()))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
@ -606,7 +606,8 @@ class FieldTest(unittest.TestCase):
|
|||||||
name = StringField()
|
name = StringField()
|
||||||
|
|
||||||
class CategoryList(Document):
|
class CategoryList(Document):
|
||||||
categories = SortedListField(EmbeddedDocumentField(Category), ordering='count', reverse=True)
|
categories = SortedListField(EmbeddedDocumentField(Category),
|
||||||
|
ordering='count', reverse=True)
|
||||||
name = StringField()
|
name = StringField()
|
||||||
|
|
||||||
catlist = CategoryList(name="Top categories")
|
catlist = CategoryList(name="Top categories")
|
||||||
@ -1616,8 +1617,9 @@ class FieldTest(unittest.TestCase):
|
|||||||
"""Ensure that value is in a container of allowed values.
|
"""Ensure that value is in a container of allowed values.
|
||||||
"""
|
"""
|
||||||
class Shirt(Document):
|
class Shirt(Document):
|
||||||
size = StringField(max_length=3, choices=(('S', 'Small'), ('M', 'Medium'), ('L', 'Large'),
|
size = StringField(max_length=3, choices=(
|
||||||
('XL', 'Extra Large'), ('XXL', 'Extra Extra Large')))
|
('S', 'Small'), ('M', 'Medium'), ('L', 'Large'),
|
||||||
|
('XL', 'Extra Large'), ('XXL', 'Extra Extra Large')))
|
||||||
|
|
||||||
Shirt.drop_collection()
|
Shirt.drop_collection()
|
||||||
|
|
||||||
@ -1633,12 +1635,15 @@ class FieldTest(unittest.TestCase):
|
|||||||
Shirt.drop_collection()
|
Shirt.drop_collection()
|
||||||
|
|
||||||
def test_choices_get_field_display(self):
|
def test_choices_get_field_display(self):
|
||||||
"""Test dynamic helper for returning the display value of a choices field.
|
"""Test dynamic helper for returning the display value of a choices
|
||||||
|
field.
|
||||||
"""
|
"""
|
||||||
class Shirt(Document):
|
class Shirt(Document):
|
||||||
size = StringField(max_length=3, choices=(('S', 'Small'), ('M', 'Medium'), ('L', 'Large'),
|
size = StringField(max_length=3, choices=(
|
||||||
('XL', 'Extra Large'), ('XXL', 'Extra Extra Large')))
|
('S', 'Small'), ('M', 'Medium'), ('L', 'Large'),
|
||||||
style = StringField(max_length=3, choices=(('S', 'Small'), ('B', 'Baggy'), ('W', 'wide')), default='S')
|
('XL', 'Extra Large'), ('XXL', 'Extra Extra Large')))
|
||||||
|
style = StringField(max_length=3, choices=(
|
||||||
|
('S', 'Small'), ('B', 'Baggy'), ('W', 'wide')), default='S')
|
||||||
|
|
||||||
Shirt.drop_collection()
|
Shirt.drop_collection()
|
||||||
|
|
||||||
@ -1665,7 +1670,8 @@ class FieldTest(unittest.TestCase):
|
|||||||
"""Ensure that value is in a container of allowed values.
|
"""Ensure that value is in a container of allowed values.
|
||||||
"""
|
"""
|
||||||
class Shirt(Document):
|
class Shirt(Document):
|
||||||
size = StringField(max_length=3, choices=('S', 'M', 'L', 'XL', 'XXL'))
|
size = StringField(max_length=3,
|
||||||
|
choices=('S', 'M', 'L', 'XL', 'XXL'))
|
||||||
|
|
||||||
Shirt.drop_collection()
|
Shirt.drop_collection()
|
||||||
|
|
||||||
@ -1681,11 +1687,15 @@ class FieldTest(unittest.TestCase):
|
|||||||
Shirt.drop_collection()
|
Shirt.drop_collection()
|
||||||
|
|
||||||
def test_simple_choices_get_field_display(self):
|
def test_simple_choices_get_field_display(self):
|
||||||
"""Test dynamic helper for returning the display value of a choices field.
|
"""Test dynamic helper for returning the display value of a choices
|
||||||
|
field.
|
||||||
"""
|
"""
|
||||||
class Shirt(Document):
|
class Shirt(Document):
|
||||||
size = StringField(max_length=3, choices=('S', 'M', 'L', 'XL', 'XXL'))
|
size = StringField(max_length=3,
|
||||||
style = StringField(max_length=3, choices=('Small', 'Baggy', 'wide'), default='Small')
|
choices=('S', 'M', 'L', 'XL', 'XXL'))
|
||||||
|
style = StringField(max_length=3,
|
||||||
|
choices=('Small', 'Baggy', 'wide'),
|
||||||
|
default='Small')
|
||||||
|
|
||||||
Shirt.drop_collection()
|
Shirt.drop_collection()
|
||||||
|
|
||||||
@ -1736,7 +1746,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
self.assertTrue(putfile == result)
|
self.assertTrue(putfile == result)
|
||||||
self.assertEqual(result.the_file.read(), text)
|
self.assertEqual(result.the_file.read(), text)
|
||||||
self.assertEqual(result.the_file.content_type, content_type)
|
self.assertEqual(result.the_file.content_type, content_type)
|
||||||
result.the_file.delete() # Remove file from GridFS
|
result.the_file.delete() # Remove file from GridFS
|
||||||
PutFile.objects.delete()
|
PutFile.objects.delete()
|
||||||
|
|
||||||
# Ensure file-like objects are stored
|
# Ensure file-like objects are stored
|
||||||
@ -1801,7 +1811,6 @@ class FieldTest(unittest.TestCase):
|
|||||||
the_file = FileField()
|
the_file = FileField()
|
||||||
DemoFile.objects.create()
|
DemoFile.objects.create()
|
||||||
|
|
||||||
|
|
||||||
def test_file_field_no_default(self):
|
def test_file_field_no_default(self):
|
||||||
|
|
||||||
class GridDocument(Document):
|
class GridDocument(Document):
|
||||||
@ -1817,7 +1826,6 @@ class FieldTest(unittest.TestCase):
|
|||||||
doc_a = GridDocument()
|
doc_a = GridDocument()
|
||||||
doc_a.save()
|
doc_a.save()
|
||||||
|
|
||||||
|
|
||||||
doc_b = GridDocument.objects.with_id(doc_a.id)
|
doc_b = GridDocument.objects.with_id(doc_a.id)
|
||||||
doc_b.the_file.replace(f, filename='doc_b')
|
doc_b.the_file.replace(f, filename='doc_b')
|
||||||
doc_b.save()
|
doc_b.save()
|
||||||
@ -1859,7 +1867,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
|
|
||||||
# Second instance
|
# Second instance
|
||||||
test_file_dupe = TestFile()
|
test_file_dupe = TestFile()
|
||||||
data = test_file_dupe.the_file.read() # Should be None
|
data = test_file_dupe.the_file.read() # Should be None
|
||||||
|
|
||||||
self.assertTrue(test_file.name != test_file_dupe.name)
|
self.assertTrue(test_file.name != test_file_dupe.name)
|
||||||
self.assertTrue(test_file.the_file.read() != data)
|
self.assertTrue(test_file.the_file.read() != data)
|
||||||
@ -2328,7 +2336,6 @@ class FieldTest(unittest.TestCase):
|
|||||||
self.assertEqual(error_dict['comments'][1]['content'],
|
self.assertEqual(error_dict['comments'][1]['content'],
|
||||||
u'Field is required')
|
u'Field is required')
|
||||||
|
|
||||||
|
|
||||||
post.comments[1].content = 'here we go'
|
post.comments[1].content = 'here we go'
|
||||||
post.validate()
|
post.validate()
|
||||||
|
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
import sys
|
import sys
|
||||||
sys.path[0:0] = [""]
|
sys.path[0:0] = [""]
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
import uuid
|
||||||
|
from nose.plugins.skip import SkipTest
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
@ -74,7 +77,6 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
def test_generic_reference():
|
def test_generic_reference():
|
||||||
list(BlogPost.objects(author2__name="test"))
|
list(BlogPost.objects(author2__name="test"))
|
||||||
|
|
||||||
|
|
||||||
def test_find(self):
|
def test_find(self):
|
||||||
"""Ensure that a query returns a valid set of results.
|
"""Ensure that a query returns a valid set of results.
|
||||||
"""
|
"""
|
||||||
@ -3672,6 +3674,72 @@ class QueryFieldListTest(unittest.TestCase):
|
|||||||
self.assertRaises(ConfigurationError, Bar.objects,
|
self.assertRaises(ConfigurationError, Bar.objects,
|
||||||
read_preference='Primary')
|
read_preference='Primary')
|
||||||
|
|
||||||
|
def test_json_simple(self):
|
||||||
|
|
||||||
|
class Embedded(EmbeddedDocument):
|
||||||
|
string = StringField()
|
||||||
|
|
||||||
|
class Doc(Document):
|
||||||
|
string = StringField()
|
||||||
|
embedded_field = EmbeddedDocumentField(Embedded)
|
||||||
|
|
||||||
|
Doc.drop_collection()
|
||||||
|
Doc(string="Hi", embedded_field=Embedded(string="Hi")).save()
|
||||||
|
Doc(string="Bye", embedded_field=Embedded(string="Bye")).save()
|
||||||
|
|
||||||
|
Doc().save()
|
||||||
|
json_data = Doc.objects.to_json()
|
||||||
|
doc_objects = list(Doc.objects)
|
||||||
|
|
||||||
|
self.assertEqual(doc_objects, Doc.objects.from_json(json_data))
|
||||||
|
|
||||||
|
def test_json_complex(self):
|
||||||
|
if pymongo.version_tuple[0] <= 2 and pymongo.version_tuple[1] <= 3:
|
||||||
|
raise SkipTest("Need pymongo 2.4 as has a fix for DBRefs")
|
||||||
|
|
||||||
|
class EmbeddedDoc(EmbeddedDocument):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Simple(Document):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Doc(Document):
|
||||||
|
string_field = StringField(default='1')
|
||||||
|
int_field = IntField(default=1)
|
||||||
|
float_field = FloatField(default=1.1)
|
||||||
|
boolean_field = BooleanField(default=True)
|
||||||
|
datetime_field = DateTimeField(default=datetime.now)
|
||||||
|
embedded_document_field = EmbeddedDocumentField(EmbeddedDoc,
|
||||||
|
default=lambda: EmbeddedDoc())
|
||||||
|
list_field = ListField(default=lambda: [1, 2, 3])
|
||||||
|
dict_field = DictField(default=lambda: {"hello": "world"})
|
||||||
|
objectid_field = ObjectIdField(default=ObjectId)
|
||||||
|
reference_field = ReferenceField(Simple, default=lambda:
|
||||||
|
Simple().save())
|
||||||
|
map_field = MapField(IntField(), default=lambda: {"simple": 1})
|
||||||
|
decimal_field = DecimalField(default=1.0)
|
||||||
|
complex_datetime_field = ComplexDateTimeField(default=datetime.now)
|
||||||
|
url_field = URLField(default="http://mongoengine.org")
|
||||||
|
dynamic_field = DynamicField(default=1)
|
||||||
|
generic_reference_field = GenericReferenceField(
|
||||||
|
default=lambda: Simple().save())
|
||||||
|
sorted_list_field = SortedListField(IntField(),
|
||||||
|
default=lambda: [1, 2, 3])
|
||||||
|
email_field = EmailField(default="ross@example.com")
|
||||||
|
geo_point_field = GeoPointField(default=lambda: [1, 2])
|
||||||
|
sequence_field = SequenceField()
|
||||||
|
uuid_field = UUIDField(default=uuid.uuid4)
|
||||||
|
generic_embedded_document_field = GenericEmbeddedDocumentField(
|
||||||
|
default=lambda: EmbeddedDoc())
|
||||||
|
|
||||||
|
Simple.drop_collection()
|
||||||
|
Doc.drop_collection()
|
||||||
|
|
||||||
|
Doc().save()
|
||||||
|
json_data = Doc.objects.to_json()
|
||||||
|
doc_objects = list(Doc.objects)
|
||||||
|
|
||||||
|
self.assertEqual(doc_objects, Doc.objects.from_json(json_data))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
Loading…
x
Reference in New Issue
Block a user