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:
Ross Lawley 2012-11-08 12:04:14 +00:00
parent 4b45c0cd14
commit b8d53a6f0d
8 changed files with 201 additions and 20 deletions

View File

@ -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)

View File

@ -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)):

View File

@ -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:

View File

@ -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()

View 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()

View File

@ -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,7 +1617,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', 'Small'), ('M', 'Medium'), ('L', 'Large'), size = StringField(max_length=3, choices=(
('S', 'Small'), ('M', 'Medium'), ('L', 'Large'),
('XL', 'Extra Large'), ('XXL', 'Extra Extra 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=(
('S', 'Small'), ('M', 'Medium'), ('L', 'Large'),
('XL', 'Extra Large'), ('XXL', 'Extra Extra Large'))) ('XL', 'Extra Large'), ('XXL', 'Extra Extra Large')))
style = StringField(max_length=3, choices=(('S', 'Small'), ('B', 'Baggy'), ('W', 'wide')), default='S') 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()
@ -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()
@ -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()

View File

@ -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__':