From f884839d17d84eca9b90e7147ef8eb756d58ea87 Mon Sep 17 00:00:00 2001 From: Stefan Wojcik Date: Fri, 21 Jun 2019 15:30:44 +0200 Subject: [PATCH] Implement BaseDocument.to_dict `BaseDocument.to_dict` serializes a document/embedded document into a dict, which can be easily consumed by other modules (which in this case don't need to be aware of MongoEngine-specific object types). The output dict contains key-value pairs where: * Keys are field names as they're defined on the document (as opposed to e.g. how they're stored in MongoDB). * Values are field values in their deserialized form (i.e. the same form that you get when you access `doc.some_field_name`). --- mongoengine/base/document.py | 40 +++++++++++++++----- tests/document/instance.py | 71 ++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 9 deletions(-) diff --git a/mongoengine/base/document.py b/mongoengine/base/document.py index 057258f5..bae05661 100644 --- a/mongoengine/base/document.py +++ b/mongoengine/base/document.py @@ -309,9 +309,7 @@ class BaseDocument(object): return self._data['_text_score'] def to_mongo(self, use_db_field=True, fields=None): - """ - Return as SON data ready for use with MongoDB. - """ + """Return as SON data ready for use with MongoDB.""" fields = fields or [] data = SON() @@ -412,12 +410,35 @@ class BaseDocument(object): message = 'ValidationError (%s:%s) ' % (self._class_name, pk) raise ValidationError(message, errors=errors) + def to_dict(self): + """Serialize this document into a dict. + + Return field names as they're defined on the document (as opposed to + e.g. how they're stored in MongoDB). Return values in their + deserialized form (i.e. the same form that you get when you access + `doc.some_field_name`). Serialize embedded documents recursively. + + The resultant dict can be consumed easily by other modules which + don't need to be aware of MongoEngine-specific object types. + + :return dict: dictionary of field name & value pairs. + """ + data_dict = {} + for field_name in self._fields: + value = getattr(self, field_name) + if isinstance(value, BaseDocument): + data_dict[field_name] = value.to_dict() + else: + data_dict[field_name] = value + return data_dict + def to_json(self, *args, **kwargs): """Convert this document to JSON. :param use_db_field: Serialize field names as they appear in MongoDB (as opposed to attribute names on this document). Defaults to True. + :return str: string representing the jsonified document. """ use_db_field = kwargs.pop('use_db_field', True) return json_util.dumps(self.to_mongo(use_db_field), *args, **kwargs) @@ -426,12 +447,13 @@ class BaseDocument(object): def from_json(cls, json_data, created=False): """Converts json data to a Document instance - :param json_data: The json data to load into the Document - :param created: If True, the document will be considered as a brand new document - If False and an id is provided, it will consider that the data being - loaded corresponds to what's already in the database (This has an impact of subsequent call to .save()) - If False and no id is provided, it will consider the data as a new document - (default ``False``) + :param str json_data: The json data to load into the Document + :param bool created: If True, the document will be considered as + a brand new document. If False and an ID is provided, it will + consider that the data being loaded corresponds to what's already + in the database (This has an impact of subsequent call to .save()) + If False and no id is provided, it will consider the data as a new + document (default ``False``) """ return cls._from_son(json_util.loads(json_data), created=created) diff --git a/tests/document/instance.py b/tests/document/instance.py index 02617b67..f844743d 100644 --- a/tests/document/instance.py +++ b/tests/document/instance.py @@ -3525,5 +3525,76 @@ class InstanceTest(MongoDBTestCase): User.objects().select_related() +class DocumentToDictTest(MongoDBTestCase): + """Class for testing the BaseDocument.to_dict method.""" + + def test_to_dict(self): + class Person(Document): + name = StringField() + age = IntField() + + p = Person(name='Tom', age=30) + self.assertEqual(p.to_dict(), {'id': None, 'name': 'Tom', 'age': 30}) + + def test_to_dict_with_a_persisted_doc(self): + class Person(Document): + name = StringField() + age = IntField() + + p = Person.objects.create(name='Tom', age=30) + p_dict = p.to_dict() + self.assertTrue(p_dict['id']) + self.assertEqual(p_dict['name'], 'Tom') + self.assertEqual(p_dict['age'], 30) + + def test_to_dict_empty_doc(self): + class Person(Document): + name = StringField() + age = IntField() + + p = Person() + self.assertEqual(p.to_dict(), {'id': None, 'name': None, 'age': None}) + + def test_to_dict_with_default_values(self): + class Person(Document): + name = StringField(default='Unknown') + age = IntField(default=0) + + p = Person() + self.assertEqual( + p.to_dict(), + {'id': None, 'name': 'Unknown', 'age': 0} + ) + + def test_to_dict_with_a_db_field(self): + class Person(Document): + name = StringField(db_field='db_name') + + p = Person(name='Tom') + self.assertEqual(p.to_dict(), {'id': None, 'name': 'Tom'}) + + def test_to_dict_with_a_primary_key(self): + class Person(Document): + username = StringField(primary_key=True) + + p = Person(username='tomtom') + self.assertEqual(p.to_dict(), {'username': 'tomtom'}) + + def test_to_dict_with_an_embedded_document(self): + class Book(EmbeddedDocument): + title = StringField() + + class Author(Document): + name = StringField() + book = EmbeddedDocumentField(Book) + + a = Author(name='Yuval', book=Book(title='Sapiens')) + self.assertEqual(a.to_dict(), { + 'id': None, + 'name': 'Yuval', + 'book': {'title': 'Sapiens'} + }) + + if __name__ == '__main__': unittest.main()